Merge "Use flags.aconfig in platform for android networking" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 654b08f..b8538fb 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -27,6 +27,7 @@
     ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
     ":android.security.flags-aconfig-java{.generated_srcjars}",
     ":android.service.chooser.flags-aconfig-java{.generated_srcjars}",
+    ":android.service.dreams.flags-aconfig-java{.generated_srcjars}",
     ":android.service.notification.flags-aconfig-java{.generated_srcjars}",
     ":android.view.flags-aconfig-java{.generated_srcjars}",
     ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
@@ -747,6 +748,19 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Dreams
+aconfig_declarations {
+    name: "android.service.dreams.flags-aconfig",
+    package: "android.service.dreams",
+    srcs: ["core/java/android/service/dreams/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.service.dreams.flags-aconfig-java",
+    aconfig_declarations: "android.service.dreams.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Notifications
 aconfig_declarations {
     name: "android.service.notification.flags-aconfig",
diff --git a/Android.bp b/Android.bp
index fa7c97d..676a0f5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -106,7 +106,7 @@
         ":android.hardware.radio.voice-V3-java-source",
         ":android.hardware.security.keymint-V3-java-source",
         ":android.hardware.security.secureclock-V1-java-source",
-        ":android.hardware.thermal-V1-java-source",
+        ":android.hardware.thermal-V2-java-source",
         ":android.hardware.tv.tuner-V2-java-source",
         ":android.security.apc-java-source",
         ":android.security.authorization-java-source",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index ad3e422..03891bb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -314,9 +314,15 @@
         if (mPackageStoppedState.contains(uid, packageName)) {
             return mPackageStoppedState.get(uid, packageName);
         }
-        final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
-        mPackageStoppedState.add(uid, packageName, isStopped);
-        return isStopped;
+
+        try {
+            final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
+            mPackageStoppedState.add(uid, packageName, isStopped);
+            return isStopped;
+        } catch (IllegalArgumentException e) {
+            Slog.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
+            return false;
+        }
     }
 
     @GuardedBy("mLock")
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 1c29982..8ddbf69 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -36,6 +36,7 @@
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.UidObserver;
+import android.app.job.JobInfo;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
@@ -772,18 +773,23 @@
         if (!jobStatus.shouldTreatAsExpeditedJob()) {
             // If quota is currently "free", then the job can run for the full amount of time,
             // regardless of bucket (hence using charging instead of isQuotaFreeLocked()).
-            if (mService.isBatteryCharging()
-                    // The top and foreground cases here were added because apps in those states
-                    // aren't really restricted and the work could be something the user is
-                    // waiting for. Now that user-initiated jobs are a defined concept, we may
-                    // not need these exemptions as much. However, UIJs are currently limited
-                    // (as of UDC) to data transfer work. There may be other work that could
-                    // rely on this exception. Once we add more UIJ types, we can re-evaluate
-                    // the need for these exceptions.
-                    // TODO: re-evaluate the need for these exceptions
-                    || mTopAppCache.get(jobStatus.getSourceUid())
+            if (mService.isBatteryCharging()) {
+                return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
+            }
+            // The top and foreground cases here were added because apps in those states
+            // aren't really restricted and the work could be something the user is
+            // waiting for. Now that user-initiated jobs are a defined concept, we may
+            // not need these exemptions as much. However, UIJs are currently limited
+            // (as of UDC) to data transfer work. There may be other work that could
+            // rely on this exception. Once we add more UIJ types, we can re-evaluate
+            // the need for these exceptions.
+            // TODO: re-evaluate the need for these exceptions
+            final boolean isInPrivilegedState = mTopAppCache.get(jobStatus.getSourceUid())
                     || isTopStartedJobLocked(jobStatus)
-                    || isUidInForeground(jobStatus.getSourceUid())) {
+                    || isUidInForeground(jobStatus.getSourceUid());
+            final boolean isJobImportant = jobStatus.getEffectivePriority() >= JobInfo.PRIORITY_HIGH
+                    || (jobStatus.getFlags() & JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0;
+            if (isInPrivilegedState && isJobImportant) {
                 return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
             }
             return getTimeUntilQuotaConsumedLocked(
@@ -2549,7 +2555,25 @@
          */
         @Override
         public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) {
-            mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event).sendToTarget();
+            // Skip posting a message to the handler for events we don't care about.
+            switch (event.getEventType()) {
+                case UsageEvents.Event.ACTIVITY_RESUMED:
+                case UsageEvents.Event.ACTIVITY_PAUSED:
+                case UsageEvents.Event.ACTIVITY_STOPPED:
+                case UsageEvents.Event.ACTIVITY_DESTROYED:
+                case UsageEvents.Event.USER_INTERACTION:
+                case UsageEvents.Event.CHOOSER_ACTION:
+                case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+                case UsageEvents.Event.NOTIFICATION_SEEN:
+                    mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event)
+                            .sendToTarget();
+                    break;
+                default:
+                    if (DEBUG) {
+                        Slog.d(TAG, "Dropping event " + event.getEventType());
+                    }
+                    break;
+            }
         }
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index b8397d2..357e1396 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -316,8 +316,25 @@
                  */
                 @Override
                 public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) {
-                    mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event)
-                            .sendToTarget();
+                    // Skip posting a message to the handler for events we don't care about.
+                    switch (event.getEventType()) {
+                        case UsageEvents.Event.ACTIVITY_RESUMED:
+                        case UsageEvents.Event.ACTIVITY_PAUSED:
+                        case UsageEvents.Event.ACTIVITY_STOPPED:
+                        case UsageEvents.Event.ACTIVITY_DESTROYED:
+                        case UsageEvents.Event.USER_INTERACTION:
+                        case UsageEvents.Event.CHOOSER_ACTION:
+                        case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+                        case UsageEvents.Event.NOTIFICATION_SEEN:
+                            mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event)
+                                    .sendToTarget();
+                            break;
+                        default:
+                            if (DEBUG) {
+                                Slog.d(TAG, "Dropping event " + event.getEventType());
+                            }
+                            break;
+                    }
                 }
             };
 
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index 9ffb704..43caaec 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -23,35 +23,43 @@
 /** Usage: extract-flagged-apis <api text file> <output .pb file> */
 fun main(args: Array<String>) {
     var cb = ApiFile.parseApi(listOf(File(args[0])))
-    val flagToApi = mutableMapOf<String, MutableList<String>>()
-    cb.getPackages()
-        .allClasses()
-        .filter { it.methods().size > 0 }
-        .forEach {
-            for (method in it.methods()) {
-                val flagValue =
-                    method.modifiers
-                        .findAnnotation("android.annotation.FlaggedApi")
-                        ?.findAttribute("value")
-                        ?.value
-                        ?.value()
-                if (flagValue != null && flagValue is String) {
-                    val methodQualifiedName = "${it.qualifiedName()}.${method.name()}"
-                    if (flagToApi.containsKey(flagValue)) {
-                        flagToApi.get(flagValue)?.add(methodQualifiedName)
-                    } else {
-                        flagToApi.put(flagValue, mutableListOf(methodQualifiedName))
+    var builder = FlagApiMap.newBuilder()
+    for (pkg in cb.getPackages().packages) {
+        var packageName = pkg.qualifiedName()
+        pkg.allClasses()
+            .filter { it.methods().size > 0 }
+            .forEach {
+                for (method in it.methods()) {
+                    val flagValue =
+                        method.modifiers
+                            .findAnnotation("android.annotation.FlaggedApi")
+                            ?.findAttribute("value")
+                            ?.value
+                            ?.value()
+                    if (flagValue != null && flagValue is String) {
+                        var api =
+                            JavaMethod.newBuilder()
+                                .setPackageName(packageName)
+                                .setClassName(it.fullName())
+                                .setMethodName(method.name())
+                        for (param in method.parameters()) {
+                            api.addParameterTypes(param.type().toTypeString())
+                        }
+                        if (builder.containsFlagToApi(flagValue)) {
+                            var updatedApis =
+                                builder
+                                    .getFlagToApiOrThrow(flagValue)
+                                    .toBuilder()
+                                    .addJavaMethods(api)
+                                    .build()
+                            builder.putFlagToApi(flagValue, updatedApis)
+                        } else {
+                            var apis = FlaggedApis.newBuilder().addJavaMethods(api).build()
+                            builder.putFlagToApi(flagValue, apis)
+                        }
                     }
                 }
             }
-        }
-    var builder = FlagApiMap.newBuilder()
-    for (flag in flagToApi.keys) {
-        var flaggedApis = FlaggedApis.newBuilder()
-        for (method in flagToApi.get(flag).orEmpty()) {
-            flaggedApis.addFlaggedApi(FlaggedApi.newBuilder().setQualifiedName(method))
-        }
-        builder.putFlagToApi(flag, flaggedApis.build())
     }
     val flagApiMap = builder.build()
     FileWriter(args[1]).use { it.write(flagApiMap.toString()) }
diff --git a/api/coverage/tools/extract_flagged_apis.proto b/api/coverage/tools/extract_flagged_apis.proto
index a858108..031d621 100644
--- a/api/coverage/tools/extract_flagged_apis.proto
+++ b/api/coverage/tools/extract_flagged_apis.proto
@@ -25,10 +25,13 @@
 }
 
 message FlaggedApis {
-  repeated FlaggedApi flagged_api = 1;
+  repeated JavaMethod java_methods = 1;
 }
 
-message FlaggedApi {
-  string qualified_name = 1;
+message JavaMethod {
+  string package_name = 1;
+  string class_name = 2;
+  string method_name = 3;
+  repeated string parameter_types = 4;
 }
 
diff --git a/core/api/current.txt b/core/api/current.txt
index 61c7ec6..d490c3f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1216,6 +1216,7 @@
     field public static final int opticalInsetLeft = 16844168; // 0x1010588
     field public static final int opticalInsetRight = 16844170; // 0x101058a
     field public static final int opticalInsetTop = 16844169; // 0x1010589
+    field @FlaggedApi("android.content.pm.sdk_lib_independence") public static final int optional;
     field public static final int order = 16843242; // 0x10101ea
     field public static final int orderInCategory = 16843231; // 0x10101df
     field public static final int ordering = 16843490; // 0x10102e2
@@ -4467,7 +4468,7 @@
     method public void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
     method public android.net.Uri onProvideReferrer();
     method public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[]);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[], int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[], int);
     method @CallSuper protected void onRestart();
     method protected void onRestoreInstanceState(@NonNull android.os.Bundle);
     method public void onRestoreInstanceState(@Nullable android.os.Bundle, @Nullable android.os.PersistableBundle);
@@ -4509,7 +4510,7 @@
     method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent);
     method public void requestFullscreenMode(int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>);
     method public final void requestPermissions(@NonNull String[], int);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public final void requestPermissions(@NonNull String[], int, int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public final void requestPermissions(@NonNull String[], int, int);
     method public final void requestShowKeyboardShortcuts();
     method @Deprecated public boolean requestVisibleBehind(boolean);
     method public final boolean requestWindowFeature(int);
@@ -4557,7 +4558,7 @@
     method public void setVrModeEnabled(boolean, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean shouldDockBigOverlays();
     method public boolean shouldShowRequestPermissionRationale(@NonNull String);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public boolean shouldShowRequestPermissionRationale(@NonNull String, int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public boolean shouldShowRequestPermissionRationale(@NonNull String, int);
     method public boolean shouldUpRecreateTask(android.content.Intent);
     method public boolean showAssist(android.os.Bundle);
     method @Deprecated public final void showDialog(int);
@@ -9838,7 +9839,7 @@
     method public int describeContents();
     method public void enforceCallingUid();
     method @Nullable public String getAttributionTag();
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public int getDeviceId();
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public int getDeviceId();
     method @Nullable public android.content.AttributionSource getNext();
     method @Nullable public String getPackageName();
     method public int getPid();
@@ -9854,7 +9855,7 @@
     ctor public AttributionSource.Builder(@NonNull android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource build();
     method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull public android.content.AttributionSource.Builder setDeviceId(int);
     method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
     method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
@@ -36910,6 +36911,7 @@
     field public static final String ACTION_REGIONAL_PREFERENCES_SETTINGS = "android.settings.REGIONAL_PREFERENCES_SETTINGS";
     field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
     field public static final String ACTION_REQUEST_MANAGE_MEDIA = "android.settings.REQUEST_MANAGE_MEDIA";
+    field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
     field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM";
     field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
     field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
@@ -53879,9 +53881,11 @@
     method @Deprecated public android.view.Display getDefaultDisplay();
     method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
     method public default boolean isCrossWindowBlurEnabled();
+    method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void registerTrustedPresentationListener(@NonNull android.os.IBinder, @NonNull android.window.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
     method public void removeViewImmediate(android.view.View);
+    method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
     field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
@@ -60862,6 +60866,16 @@
     method public void markSyncReady();
   }
 
+  @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public final class TrustedPresentationThresholds implements android.os.Parcelable {
+    ctor @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
+    method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public int describeContents();
+    method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @NonNull public static final android.os.Parcelable.Creator<android.window.TrustedPresentationThresholds> CREATOR;
+    field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) public final float minAlpha;
+    field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) public final float minFractionRendered;
+    field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @IntRange(from=1) public final int stabilityRequirementMs;
+  }
+
 }
 
 package javax.microedition.khronos.egl {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c64f230..9a65388 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3232,6 +3232,7 @@
     method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
     method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
     method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
+    method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDisplayImePolicy(int, int);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
   }
@@ -4008,7 +4009,7 @@
     field public static final int DELETE_FAILED_OWNER_BLOCKED = -4; // 0xfffffffc
     field public static final int DELETE_KEEP_DATA = 1; // 0x1
     field public static final int DELETE_SUCCEEDED = 1; // 0x1
-    field @FlaggedApi("android.permission.flags.device_aware_permission_apis") public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID";
+    field @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID";
     field public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
@@ -4114,7 +4115,7 @@
 
   public static interface PackageManager.OnPermissionsChangedListener {
     method public void onPermissionsChanged(int);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public default void onPermissionsChanged(int, @NonNull String);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onPermissionsChanged(int, @NonNull String);
   }
 
   public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable {
@@ -4540,6 +4541,7 @@
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final class RequestProcessor.Request {
     ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public RequestProcessor.Request(@NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>>, int);
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>> getParameters();
   }
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface RequestProcessor.RequestCallback {
@@ -10925,13 +10927,13 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetUnusedAppCount(@NonNull java.util.function.IntConsumer);
     method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable);
     method @Deprecated @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int);
     method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
     method @Deprecated @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable);
     method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
@@ -17177,6 +17179,9 @@
     method @NonNull public default java.util.List<android.content.ComponentName> notifyScreenshotListeners(int);
     method public default void registerTaskFpsCallback(@IntRange(from=0) int, @NonNull java.util.concurrent.Executor, @NonNull android.window.TaskFpsCallback);
     method public default void unregisterTaskFpsCallback(@NonNull android.window.TaskFpsCallback);
+    field public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; // 0x1
+    field public static final int DISPLAY_IME_POLICY_HIDE = 2; // 0x2
+    field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0
   }
 
   public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 3f2376d..39f2737 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -903,7 +903,7 @@
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder);
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource);
     ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource);
-    ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
+    ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
     method public void enforceCallingPid();
   }
 
@@ -3619,9 +3619,6 @@
     method public default void setShouldShowWithInsecureKeyguard(int, boolean);
     method public default boolean shouldShowSystemDecors(int);
     method @Nullable public default android.graphics.Bitmap snapshotTaskForRecents(@IntRange(from=0) int);
-    field public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; // 0x1
-    field public static final int DISPLAY_IME_POLICY_HIDE = 2; // 0x2
-    field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0
     field public static final int LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP = 600; // 0x258
   }
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index cb08dad..ffed405 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5572,7 +5572,7 @@
      * @see #shouldShowRequestPermissionRationale
      * @see Context#DEVICE_ID_DEFAULT
      */
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public final void requestPermissions(@NonNull String[] permissions, int requestCode,
             int deviceId) {
         if (requestCode < 0) {
@@ -5645,7 +5645,7 @@
      *
      * @see #requestPermissions
      */
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
             @NonNull int[] grantResults, int deviceId) {
         onRequestPermissionsResult(requestCode, permissions, grantResults);
@@ -5678,7 +5678,7 @@
      * @see #requestPermissions
      * @see #onRequestPermissionsResult
      */
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public boolean shouldShowRequestPermissionRationale(@NonNull String permission, int deviceId) {
         final PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager()
                 : createDeviceContext(deviceId).getPackageManager();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index adaaee2..8b39ed6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1544,11 +1544,12 @@
         @Override
         public void dumpMemInfo(ParcelFileDescriptor pfd, Debug.MemoryInfo mem, boolean checkin,
                 boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
-                boolean dumpUnreachable, String[] args) {
+                boolean dumpUnreachable, boolean dumpAllocatorStats, String[] args) {
             FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor());
             PrintWriter pw = new FastPrintWriter(fout);
             try {
-                dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable);
+                dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly,
+                            dumpUnreachable, dumpAllocatorStats);
             } finally {
                 pw.flush();
                 IoUtils.closeQuietly(pfd);
@@ -1557,7 +1558,8 @@
 
         @NeverCompile
         private void dumpMemInfo(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin,
-                boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable) {
+                boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
+                boolean dumpUnreachable, boolean dumpAllocatorStats) {
             long nativeMax = Debug.getNativeHeapSize() / 1024;
             long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
             long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
@@ -1710,6 +1712,9 @@
                 pw.println(" Unreachable memory");
                 pw.print(Debug.getUnreachableMemory(100, showContents));
             }
+            if (dumpAllocatorStats) {
+                Debug.logAllocatorStats();
+            }
         }
 
         @NeverCompile
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 75d8c10..5541e7a 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -129,7 +129,7 @@
     void scheduleTrimMemory(int level);
     void dumpMemInfo(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem, boolean checkin,
             boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable,
-            in String[] args);
+            boolean dumpAllocatorLogs, in String[] args);
     void dumpMemInfoProto(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem,
             boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable,
             in String[] args);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1e538c5..90a2659 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16640,6 +16640,7 @@
                 == DEVICE_OWNER_TYPE_FINANCED;
     }
 
+    // TODO(b/315298076): revert ag/25574027 and update the doc
     /**
      * Called by a device owner or profile owner of an organization-owned managed profile to enable
      * or disable USB data signaling for the device. When disabled, USB data connections
@@ -16649,12 +16650,11 @@
      * {@link #canUsbDataSignalingBeDisabled()} to check whether enabling or disabling USB data
      * signaling is supported on the device.
      *
-     * Starting from {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, after the USB data signaling
+     * Starting from Android 15, after the USB data signaling
      * policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
      * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
      * successfully set or not. This callback will contain:
      * <ul>
-     * li> The policy identifier {@link DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY}
      * <li> The {@link TargetUser} that this policy relates to
      * <li> The {@link PolicyUpdateResult}, which will be
      * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 3520c0b..12229b1 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -219,6 +219,10 @@
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void setShowPointerIcon(boolean showPointerIcon);
 
+    /** Sets an IME policy for the given display. */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+    void setDisplayImePolicy(int displayId, int policy);
+
     /**
      * Registers an intent interceptor that will intercept an intent attempting to launch
      * when matching the provided IntentFilter and calls the callback with the intercepted
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 003dffb..2abeeee 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -53,6 +53,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.util.ArrayMap;
+import android.view.WindowManager;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -361,6 +362,14 @@
         }
     }
 
+    void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
+        try {
+            mVirtualDevice.setDisplayImePolicy(displayId, policy);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     void addActivityListener(
             @CallbackExecutor @NonNull Executor executor,
             @NonNull VirtualDeviceManager.ActivityListener listener) {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 41c90b9..eef60f1 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -63,6 +63,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.Surface;
+import android.view.WindowManager;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -914,6 +915,24 @@
         }
 
         /**
+         * Specifies the IME behavior on the given display. By default, all displays created by
+         * virtual devices have {@link WindowManager#DISPLAY_IME_POLICY_LOCAL}.
+         *
+         * @param displayId the ID of the display to change the IME policy for. It must be owned by
+         *                  this virtual device.
+         * @param policy the IME policy to use on that display
+         * @throws SecurityException if the display is not owned by this device or is not
+         *                           {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED trusted}
+         */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
+        public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
+            if (Flags.vdmCustomIme()) {
+                mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy);
+            }
+        }
+
+        /**
          * Adds an activity listener to listen for events such as top activity change or virtual
          * display task stack became empty.
          *
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index b2074a6..a1357c9 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -173,7 +173,7 @@
 
     /** @hide */
     @TestApi
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public AttributionSource(int uid, int pid, @Nullable String packageName,
             @Nullable String attributionTag, @NonNull IBinder token,
             @Nullable String[] renouncedPermissions,
@@ -539,7 +539,7 @@
      * <p>
      * This device ID is used for permissions checking during attribution source validation.
      */
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public int getDeviceId() {
         return mAttributionSourceState.deviceId;
     }
@@ -727,7 +727,7 @@
          *
          * @return the builder
          */
-        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
         public @NonNull Builder setDeviceId(int deviceId) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x12;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f865a36..e224329 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -731,7 +731,7 @@
          * @see VirtualDeviceManager.VirtualDevice#getPersistentDeviceId()
          * @see VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT
          */
-        @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+        @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
         default void onPermissionsChanged(int uid, @NonNull String persistentDeviceId) {
             Objects.requireNonNull(persistentDeviceId);
             if (Objects.equals(persistentDeviceId,
@@ -4893,7 +4893,7 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID =
             "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID";
 
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index fdd2aa1..25ba725 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -142,8 +142,10 @@
         mName = parcel.readString8();
         mVersion = parcel.readLong();
         mType = parcel.readInt();
-        mDeclaringPackage = parcel.readParcelable(null, android.content.pm.VersionedPackage.class);
-        mDependentPackages = parcel.readArrayList(null, android.content.pm.VersionedPackage.class);
+        mDeclaringPackage =
+                parcel.readParcelable(null, android.content.pm.VersionedPackage.class);
+        mDependentPackages =
+                parcel.readArrayList(null, android.content.pm.VersionedPackage.class);
         mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR);
         mIsNative = parcel.readBoolean();
     }
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index c401268..57749d4 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -68,6 +68,8 @@
             "authAlwaysRequiredToDisableQuietMode";
     private static final String ATTR_DELETE_APP_WITH_PARENT = "deleteAppWithParent";
     private static final String ATTR_ALWAYS_VISIBLE = "alwaysVisible";
+    private static final String ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING =
+            "allowStoppingUserWithDelayedLocking";
 
     private static final String ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY =
             "crossProfileContentSharingStrategy";
@@ -89,7 +91,8 @@
             INDEX_SHOW_IN_QUIET_MODE,
             INDEX_SHOW_IN_SHARING_SURFACES,
             INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
-            INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY
+            INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY,
+            INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING,
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
@@ -110,6 +113,7 @@
     private static final int INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE = 13;
     private static final int INDEX_SHOW_IN_SHARING_SURFACES = 14;
     private static final int INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY = 15;
+    private static final int INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING = 16;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -450,6 +454,7 @@
             setCrossProfileIntentResolutionStrategy(orig.getCrossProfileIntentResolutionStrategy());
             setDeleteAppWithParent(orig.getDeleteAppWithParent());
             setAlwaysVisible(orig.getAlwaysVisible());
+            setAllowStoppingUserWithDelayedLocking(orig.getAllowStoppingUserWithDelayedLocking());
         }
         if (hasManagePermission) {
             // Add items that require MANAGE_USERS or stronger.
@@ -725,6 +730,11 @@
         this.mUpdateCrossProfileIntentFiltersOnOTA = val;
         setPresent(INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA);
     }
+    /**
+     Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during
+     OTA update between user-parent
+     */
+    private boolean mUpdateCrossProfileIntentFiltersOnOTA;
 
     /**
      * Returns whether a profile shares media with its parent user.
@@ -786,12 +796,38 @@
     }
     private boolean mAuthAlwaysRequiredToDisableQuietMode;
 
-    /*
-     Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during
-     OTA update between user-parent
+    /**
+     * Returns whether a user (usually a profile) is allowed to leave the CE storage unlocked when
+     * stopped.
+     *
+     * <p> Setting this property to true will enable the user's CE storage to remain unlocked when
+     * the user is stopped using
+     * {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int,
+     * boolean, IStopUserCallback)}.
+     *
+     * <p> When this property is false, delayed locking may still be applicable at a global
+     * level for all users via the {@code config_multiuserDelayUserDataLocking}. That is, delayed
+     * locking for a user can happen if either the device configuration is set or if this property
+     * is set. When both, the config and the property value is false, the user storage is always
+     * locked when the user is stopped.
+     * @hide
      */
-    private boolean mUpdateCrossProfileIntentFiltersOnOTA;
-
+    public boolean getAllowStoppingUserWithDelayedLocking() {
+        if (isPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING)) {
+            return mAllowStoppingUserWithDelayedLocking;
+        }
+        if (mDefaultProperties != null) {
+            return mDefaultProperties.mAllowStoppingUserWithDelayedLocking;
+        }
+        throw new SecurityException(
+                "You don't have permission to query allowStoppingUserWithDelayedLocking");
+    }
+    /** @hide */
+    public void setAllowStoppingUserWithDelayedLocking(boolean val) {
+        this.mAllowStoppingUserWithDelayedLocking = val;
+        setPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING);
+    }
+    private boolean mAllowStoppingUserWithDelayedLocking;
 
     /**
      * Returns the user's {@link CrossProfileIntentFilterAccessControlLevel}.
@@ -899,6 +935,8 @@
                 + ", mCredentialShareableWithParent=" + isCredentialShareableWithParent()
                 + ", mAuthAlwaysRequiredToDisableQuietMode="
                 + isAuthAlwaysRequiredToDisableQuietMode()
+                + ", mAllowStoppingUserWithDelayedLocking="
+                + getAllowStoppingUserWithDelayedLocking()
                 + ", mDeleteAppWithParent=" + getDeleteAppWithParent()
                 + ", mAlwaysVisible=" + getAlwaysVisible()
                 + ", mCrossProfileContentSharingStrategy=" + getCrossProfileContentSharingStrategy()
@@ -929,6 +967,8 @@
                 + isCredentialShareableWithParent());
         pw.println(prefix + "    mAuthAlwaysRequiredToDisableQuietMode="
                 + isAuthAlwaysRequiredToDisableQuietMode());
+        pw.println(prefix + "    mAllowStoppingUserWithDelayedLocking="
+                + getAllowStoppingUserWithDelayedLocking());
         pw.println(prefix + "    mDeleteAppWithParent=" + getDeleteAppWithParent());
         pw.println(prefix + "    mAlwaysVisible=" + getAlwaysVisible());
         pw.println(prefix + "    mCrossProfileContentSharingStrategy="
@@ -1005,6 +1045,9 @@
                 case ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE:
                     setAuthAlwaysRequiredToDisableQuietMode(parser.getAttributeBoolean(i));
                     break;
+                case ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING:
+                    setAllowStoppingUserWithDelayedLocking(parser.getAttributeBoolean(i));
+                    break;
                 case ATTR_DELETE_APP_WITH_PARENT:
                     setDeleteAppWithParent(parser.getAttributeBoolean(i));
                     break;
@@ -1079,6 +1122,10 @@
             serializer.attributeBoolean(null, ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
                     mAuthAlwaysRequiredToDisableQuietMode);
         }
+        if (isPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING)) {
+            serializer.attributeBoolean(null, ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING,
+                    mAllowStoppingUserWithDelayedLocking);
+        }
         if (isPresent(INDEX_DELETE_APP_WITH_PARENT)) {
             serializer.attributeBoolean(null, ATTR_DELETE_APP_WITH_PARENT,
                     mDeleteAppWithParent);
@@ -1110,6 +1157,7 @@
         dest.writeBoolean(mMediaSharedWithParent);
         dest.writeBoolean(mCredentialShareableWithParent);
         dest.writeBoolean(mAuthAlwaysRequiredToDisableQuietMode);
+        dest.writeBoolean(mAllowStoppingUserWithDelayedLocking);
         dest.writeBoolean(mDeleteAppWithParent);
         dest.writeBoolean(mAlwaysVisible);
         dest.writeInt(mCrossProfileContentSharingStrategy);
@@ -1136,6 +1184,7 @@
         mMediaSharedWithParent = source.readBoolean();
         mCredentialShareableWithParent = source.readBoolean();
         mAuthAlwaysRequiredToDisableQuietMode = source.readBoolean();
+        mAllowStoppingUserWithDelayedLocking = source.readBoolean();
         mDeleteAppWithParent = source.readBoolean();
         mAlwaysVisible = source.readBoolean();
         mCrossProfileContentSharingStrategy = source.readInt();
@@ -1183,6 +1232,7 @@
         private boolean mMediaSharedWithParent = false;
         private boolean mCredentialShareableWithParent = false;
         private boolean mAuthAlwaysRequiredToDisableQuietMode = false;
+        private boolean mAllowStoppingUserWithDelayedLocking = false;
         private boolean mDeleteAppWithParent = false;
         private boolean mAlwaysVisible = false;
         private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy =
@@ -1302,6 +1352,16 @@
             return this;
         }
 
+        /** Sets the value for {@link #mAllowStoppingUserWithDelayedLocking}
+         * @hide
+         */
+        public Builder setAllowStoppingUserWithDelayedLocking(
+                boolean allowStoppingUserWithDelayedLocking) {
+            mAllowStoppingUserWithDelayedLocking =
+                    allowStoppingUserWithDelayedLocking;
+            return this;
+        }
+
         /** Sets the value for {@link #mDeleteAppWithParent}
          * @hide
          */
@@ -1352,6 +1412,7 @@
                     mMediaSharedWithParent,
                     mCredentialShareableWithParent,
                     mAuthAlwaysRequiredToDisableQuietMode,
+                    mAllowStoppingUserWithDelayedLocking,
                     mDeleteAppWithParent,
                     mAlwaysVisible,
                     mCrossProfileContentSharingStrategy);
@@ -1372,6 +1433,7 @@
             boolean mediaSharedWithParent,
             boolean credentialShareableWithParent,
             boolean authAlwaysRequiredToDisableQuietMode,
+            boolean allowStoppingUserWithDelayedLocking,
             boolean deleteAppWithParent,
             boolean alwaysVisible,
             @CrossProfileContentSharingStrategy int crossProfileContentSharingStrategy) {
@@ -1390,6 +1452,7 @@
         setCredentialShareableWithParent(credentialShareableWithParent);
         setAuthAlwaysRequiredToDisableQuietMode(
                 authAlwaysRequiredToDisableQuietMode);
+        setAllowStoppingUserWithDelayedLocking(allowStoppingUserWithDelayedLocking);
         setDeleteAppWithParent(deleteAppWithParent);
         setAlwaysVisible(alwaysVisible);
         setCrossProfileContentSharingStrategy(crossProfileContentSharingStrategy);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index e3a5520..3ab889d 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3317,7 +3317,7 @@
      * stream configurations are the same as for applications targeting SDK version older than
      * 31.</p>
      * <p>Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} and
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations }
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">the table</a>
      * for additional mandatory stream configurations on a per-capability basis.</p>
      * <p>*1: For JPEG format, the sizes may be restricted by below conditions:</p>
      * <ul>
@@ -3436,11 +3436,12 @@
      * {@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL }
      * and {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES }.
      * This is an app-readable conversion of the mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations guideline} based on specific device level and capabilities.
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">guideline</a>.
+     * based on specific device level and capabilities.
      * Clients can use the array as a quick reference to find an appropriate camera stream
      * combination.
      * As per documentation, the stream combinations with given PREVIEW, RECORD and
@@ -3469,11 +3470,12 @@
     /**
      * <p>An array of mandatory concurrent stream combinations.
      * This is an app-readable conversion of the concurrent mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#concurrent-stream-guaranteed-configurations">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline} for each device which has its Id present in the set returned by
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#concurrent-stream-guaranteed-configurations">guideline</a>
+     * for each device which has its Id present in the set returned by
      * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds }.
      * Clients can use the array as a quick reference to find an appropriate camera stream
      * combination.
@@ -3578,7 +3580,7 @@
      * <p>If a camera device supports multi-resolution output streams for a particular format, for
      * each of its mandatory stream combinations, the camera device will support using a
      * MultiResolutionImageReader for the MAXIMUM stream of supported formats. Refer to
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs }
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs">the table</a>
      * for additional details.</p>
      * <p>To use multi-resolution input streams, the supported formats can be queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getInputFormats }.
      * A reprocessable CameraCaptureSession can then be created using an {@link android.hardware.camera2.params.InputConfiguration InputConfiguration} constructed with
@@ -3587,7 +3589,7 @@
      * {@code YUV} output, or multi-resolution {@code PRIVATE} input and multi-resolution
      * {@code PRIVATE} output, {@code JPEG} and {@code YUV} are guaranteed to be supported
      * multi-resolution output stream formats. Refer to
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs }}
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs">the table</a>
      * for details about the additional mandatory stream combinations in this case.</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      */
@@ -3700,11 +3702,12 @@
      * {@link android.hardware.camera2.CaptureRequest } has {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set
      * to {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
      * This is an app-readable conversion of the maximum resolution mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors guideline} for each device which has the
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors">guideline</a>
+     * for each device which has the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
      * capability.
      * Clients can use the array as a quick reference to find an appropriate camera stream
@@ -3727,11 +3730,12 @@
      * 10-bit output capability
      * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT }
      * This is an app-readable conversion of the 10 bit output mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations guideline} for each device which has the
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">guideline</a>
+     * for each device which has the
      * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT }
      * capability.
      * Clients can use the array as a quick reference to find an appropriate camera stream
@@ -3752,11 +3756,12 @@
      * {@code PREVIEW_STABILIZATION} in {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES android.control.availableVideoStabilizationModes}.
      * This is an app-readable conversion of the preview stabilization mandatory stream
      * combination
-     * {@link android.hardware.camera2.CameraDevice#preview-stabilization-guaranteed-stream-configurations tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#preview-stabilization-guaranteed-stream-configurations">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#preview-stabilization-guaranteed-stream-configurations guideline} for each device which supports {@code PREVIEW_STABILIZATION}
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#preview-stabilization-guaranteed-stream-configurations">guideline</a>
+     * for each device which supports {@code PREVIEW_STABILIZATION}
      * Clients can use the array as a quick reference to find an appropriate camera stream
      * combination.
      * The mandatory stream combination array will be {@code null} in case the device does not
@@ -3829,8 +3834,8 @@
      * <p>The guaranteed stream combinations related to stream use case for a camera device with
      * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE }
      * capability is documented in the camera device
-     * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline}. The application is strongly recommended to use one of the guaranteed stream
-     * combinations.
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guideline</a>.
+     * The application is strongly recommended to use one of the guaranteed stream combinations.
      * If the application creates a session with a stream combination not in the guaranteed
      * list, or with mixed DEFAULT and non-DEFAULT use cases within the same session,
      * the camera device may ignore some stream use cases due to hardware constraints
@@ -3866,11 +3871,13 @@
     /**
      * <p>An array of mandatory stream combinations with stream use cases.
      * This is an app-readable conversion of the mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations tables} with each stream's use case being set.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">tables</a>
+     * with each stream's use case being set.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline} for a camera device with
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guildeline</a>
+     * for a camera device with
      * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE }
      * capability.
      * The mandatory stream combination array will be {@code null} in case the device doesn't
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 002c0b2..bcce4b6 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1844,7 +1844,7 @@
      * Remaps Camera Ids in the CameraService.
      *
      * @hide
-    */
+     */
     @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
     public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
             throws CameraAccessException, SecurityException, IllegalArgumentException {
@@ -1852,6 +1852,29 @@
     }
 
     /**
+     * Injects session params into existing clients in the CameraService.
+     *
+     * @param cameraId       The camera id of client to inject session params into.
+     *                       If no such client exists for cameraId, no injection will
+     *                       take place.
+     * @param sessionParams  A {@link CaptureRequest} object containing the
+     *                       the sessionParams to inject into the existing client.
+     *
+     * @throws CameraAccessException    {@link CameraAccessException#CAMERA_DISCONNECTED} will be
+     *                                  thrown if camera service is not available. Further, if
+     *                                  if no such client exists for cameraId,
+     *                                  {@link CameraAccessException#CAMERA_ERROR} will be thrown.
+     * @throws SecurityException        If the caller does not have permission to inject session
+     *                                  params
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
+    public void injectSessionParams(@NonNull String cameraId, @NonNull CaptureRequest sessionParams)
+            throws CameraAccessException, SecurityException {
+        CameraManagerGlobal.get().injectSessionParams(cameraId, sessionParams);
+    }
+
+    /**
      * Reports {@link CameraExtensionSessionStats} to the {@link ICameraService} to be logged for
      * currently active session. Validation is done downstream.
      *
@@ -2110,6 +2133,30 @@
             }
         }
 
+        /** Injects session params into an existing client for cameraid. */
+        public void injectSessionParams(@NonNull String cameraId,
+                @NonNull CaptureRequest sessionParams)
+                throws CameraAccessException, SecurityException {
+            synchronized (mLock) {
+                ICameraService cameraService = getCameraService();
+                if (cameraService == null) {
+                    throw new CameraAccessException(
+                            CameraAccessException.CAMERA_DISCONNECTED,
+                            "Camera service is currently unavailable.");
+                }
+
+                try {
+                    cameraService.injectSessionParams(cameraId, sessionParams.getNativeMetadata());
+                } catch (ServiceSpecificException e) {
+                    throwAsPublicException(e);
+                } catch (RemoteException e) {
+                    throw new CameraAccessException(
+                            CameraAccessException.CAMERA_DISCONNECTED,
+                            "Camera service is currently unavailable.");
+                }
+            }
+        }
+
         private String[] extractCameraIdListLocked() {
             String[] cameraIds = null;
             int idCount = 0;
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 765a8f7..93fbe8a 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1216,8 +1216,7 @@
      * <ul>
      * <li>Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }</li>
      * <li>All mandatory stream combinations for this specific capability as per
-     *   documentation
-     *   {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations }</li>
+     *   <a href="CameraDevice#10-bit-output-additional-guaranteed-configurations">documentation</a></li>
      * <li>In case the device is not able to capture some combination of supported
      *   standard 8-bit and/or 10-bit dynamic range profiles within the same capture request,
      *   then those constraints must be listed in
@@ -1256,8 +1255,8 @@
      * </ul>
      * <p>{@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES }
      * lists all of the supported stream use cases.</p>
-     * <p>Refer to
-     * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations }
+     * <p>Refer to the
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guideline</a>
      * for the mandatory stream combinations involving stream use cases, which can also be
      * queried via {@link android.hardware.camera2.params.MandatoryStreamCombination }.</p>
      * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
@@ -1756,9 +1755,9 @@
     /**
      * <p>This camera device does not have enough capabilities to qualify as a <code>FULL</code> device or
      * better.</p>
-     * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code> tables in the
-     * {@link android.hardware.camera2.CameraDevice#limited-level-additional-guaranteed-configurations }
-     * documentation are guaranteed to be supported.</p>
+     * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#limited-level-additional-guaranteed-configurations">tables</a>
+     * in the documentation are guaranteed to be supported.</p>
      * <p>All <code>LIMITED</code> devices support the <code>BACKWARDS_COMPATIBLE</code> capability, indicating basic
      * support for color image capture. The only exception is that the device may
      * alternatively support only the <code>DEPTH_OUTPUT</code> capability, if it can only output depth
@@ -1784,9 +1783,9 @@
 
     /**
      * <p>This camera device is capable of supporting advanced imaging applications.</p>
-     * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code> tables in the
-     * {@link android.hardware.camera2.CameraDevice#full-level-additional-guaranteed-configurations }
-     * documentation are guaranteed to be supported.</p>
+     * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#full-level-additional-guaranteed-configurations">tables</a>
+     * in the documentation are guaranteed to be supported.</p>
      * <p>A <code>FULL</code> device will support below capabilities:</p>
      * <ul>
      * <li><code>BURST_CAPTURE</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
@@ -1814,9 +1813,9 @@
 
     /**
      * <p>This camera device is running in backward compatibility mode.</p>
-     * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations }
-     * documentation are supported.</p>
+     * <p>Only the stream configurations listed in the <code>LEGACY</code>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">table</a>
+     * in the documentation are supported.</p>
      * <p>A <code>LEGACY</code> device does not support per-frame control, manual sensor control, manual
      * post-processing, arbitrary cropping regions, and has relaxed performance constraints.
      * No additional capabilities beyond <code>BACKWARD_COMPATIBLE</code> will ever be listed by a
@@ -1839,9 +1838,9 @@
      * <p>This camera device is capable of YUV reprocessing and RAW data capture, in addition to
      * FULL-level capabilities.</p>
      * <p>The stream configurations listed in the <code>LEVEL_3</code>, <code>RAW</code>, <code>FULL</code>, <code>LEGACY</code> and
-     * <code>LIMITED</code> tables in the
-     * {@link android.hardware.camera2.CameraDevice#level-3-additional-guaranteed-configurations }
-     * documentation are guaranteed to be supported.</p>
+     * <code>LIMITED</code>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#level-3-additional-guaranteed-configurations">tables</a>
+     * in the documentation are guaranteed to be supported.</p>
      * <p>The following additional capabilities are guaranteed to be supported:</p>
      * <ul>
      * <li><code>YUV_REPROCESSING</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
diff --git a/core/java/android/hardware/camera2/extension/RequestProcessor.java b/core/java/android/hardware/camera2/extension/RequestProcessor.java
index 7c099d6..bf5ea12 100644
--- a/core/java/android/hardware/camera2/extension/RequestProcessor.java
+++ b/core/java/android/hardware/camera2/extension/RequestProcessor.java
@@ -250,7 +250,7 @@
          */
         @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         @NonNull
-        List<Pair<CaptureRequest.Key, Object>> getParameters() {
+        public List<Pair<CaptureRequest.Key, Object>> getParameters() {
             return mParameters;
         }
 
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 8e49c4c..134a510 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -403,14 +403,13 @@
 
     /**
      * Virtual display flag: Indicates that the display should support system decorations. Virtual
-     * displays without this flag shouldn't show home, IME or any other system decorations.
+     * displays without this flag shouldn't show home, navigation bar or wallpaper.
      * <p>This flag doesn't work without {@link #VIRTUAL_DISPLAY_FLAG_TRUSTED}</p>
      *
      * @see #createVirtualDisplay
      * @see #VIRTUAL_DISPLAY_FLAG_TRUSTED
      * @hide
      */
-    // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors
     @TestApi
     public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9;
 
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 7388b5b..9e09759 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -447,7 +447,8 @@
          * Sets whether this display supports showing home activities and wallpaper.
          *
          * <p>If set to {@code true}, then the home activity relevant to this display will be
-         * automatically launched upon the display creation.</p>
+         * automatically launched upon the display creation. If unset or set to {@code false}, the
+         * display will not host any activities upon creation.</p>
          *
          * <p>Note: setting to {@code true} requires the display to be trusted. If the display is
          * not trusted, this property is ignored.</p>
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 6a83cee..05a1abea 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -33,6 +33,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class BatteryConsumer {
 
     private static final String TAG = "BatteryConsumer";
diff --git a/core/java/android/os/CoolingDevice.java b/core/java/android/os/CoolingDevice.java
index 4ddcd9d..06ec720 100644
--- a/core/java/android/os/CoolingDevice.java
+++ b/core/java/android/os/CoolingDevice.java
@@ -55,7 +55,11 @@
             TYPE_TPU,
             TYPE_POWER_AMPLIFIER,
             TYPE_DISPLAY,
-            TYPE_SPEAKER
+            TYPE_SPEAKER,
+            TYPE_WIFI,
+            TYPE_CAMERA,
+            TYPE_FLASHLIGHT,
+            TYPE_USB_PORT
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Type {}
@@ -84,6 +88,14 @@
     public static final int TYPE_DISPLAY = CoolingType.DISPLAY;
     /** Speaker cooling device */
     public static final int TYPE_SPEAKER = CoolingType.SPEAKER;
+    /** WiFi cooling device */
+    public static final int TYPE_WIFI = CoolingType.WIFI;
+    /** Camera cooling device */
+    public static final int TYPE_CAMERA = CoolingType.CAMERA;
+    /** Flashlight cooling device */
+    public static final int TYPE_FLASHLIGHT = CoolingType.FLASHLIGHT;
+    /** USB PORT cooling device */
+    public static final int TYPE_USB_PORT = CoolingType.USB_PORT;
 
     /**
      * Verify a valid cooling device type.
@@ -91,7 +103,7 @@
      * @return true if a cooling device type is valid otherwise false.
      */
     public static boolean isValidType(@Type int type) {
-        return type >= TYPE_FAN && type <= TYPE_SPEAKER;
+        return type >= TYPE_FAN && type <= TYPE_USB_PORT;
     }
 
     public CoolingDevice(long value, @Type int type, @NonNull String name) {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index c527cb5..04d6f61 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -2702,4 +2702,13 @@
      * @hide
      */
     public static native boolean isVmapStack();
+
+    /**
+     * Log internal statistics about the allocator.
+     * @return true if the statistics were logged properly, false if not.
+     *
+     * @hide
+     */
+    public static native boolean logAllocatorStats();
+
 }
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index fc8523e..7e07e1f 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1599,7 +1599,7 @@
      * fully removed, otherwise system resources may leak.
      * @hide
      */
-    public static final native boolean sendSignalToProcessGroup(int uid, int pid, int signal);
+    public static final native int sendSignalToProcessGroup(int uid, int pid, int signal);
 
     /**
       * Freeze the cgroup for the given UID.
diff --git a/core/java/android/os/Temperature.java b/core/java/android/os/Temperature.java
index a138431..2e12278 100644
--- a/core/java/android/os/Temperature.java
+++ b/core/java/android/os/Temperature.java
@@ -79,7 +79,13 @@
             TYPE_TPU,
             TYPE_DISPLAY,
             TYPE_MODEM,
-            TYPE_SOC
+            TYPE_SOC,
+            TYPE_WIFI,
+            TYPE_CAMERA,
+            TYPE_FLASHLIGHT,
+            TYPE_SPEAKER,
+            TYPE_AMBIENT,
+            TYPE_POGO
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Type {}
@@ -101,6 +107,12 @@
     public static final int TYPE_DISPLAY = TemperatureType.DISPLAY;
     public static final int TYPE_MODEM = TemperatureType.MODEM;
     public static final int TYPE_SOC = TemperatureType.SOC;
+    public static final int TYPE_WIFI = TemperatureType.WIFI;
+    public static final int TYPE_CAMERA = TemperatureType.CAMERA;
+    public static final int TYPE_FLASHLIGHT = TemperatureType.FLASHLIGHT;
+    public static final int TYPE_SPEAKER = TemperatureType.SPEAKER;
+    public static final int TYPE_AMBIENT = TemperatureType.AMBIENT;
+    public static final int TYPE_POGO = TemperatureType.POGO;
 
     /**
      * Verify a valid Temperature type.
@@ -108,7 +120,7 @@
      * @return true if a Temperature type is valid otherwise false.
      */
     public static boolean isValidType(@Type int type) {
-        return type >= TYPE_UNKNOWN && type <= TYPE_SOC;
+        return type >= TYPE_UNKNOWN && type <= TYPE_POGO;
     }
 
     /**
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 980c13c..145981c 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -76,3 +76,11 @@
     description: "Guards the ADPF GPU APIs."
     bug: "284324521"
 }
+
+flag {
+    name: "battery_service_support_current_adb_command"
+    namespace: "backstage_power"
+    description: "Whether or not BatteryService supports adb commands for Current values."
+    is_fixed_read_only: true
+    bug: "315037695"
+}
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 9fe2599..5a12760 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -318,7 +318,7 @@
      *                 a virtual device. See {@link Context#DEVICE_ID_DEFAULT}
      */
     @BinderThread
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public void onOneTimePermissionSessionTimeout(@NonNull String packageName,
             int deviceId) {
         onOneTimePermissionSessionTimeout(packageName);
@@ -393,7 +393,7 @@
      * @see android.content.Context#revokeSelfPermissionsOnKill(java.util.Collection)
      */
     @BinderThread
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public void onRevokeSelfPermissionsOnKill(@NonNull String packageName,
             @NonNull List<String> permissions, int deviceId, @NonNull Runnable callback) {
         onRevokeSelfPermissionsOnKill(packageName, permissions, callback);
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index cbeb821..d09c229 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -1,7 +1,7 @@
 package: "android.permission.flags"
 
 flag {
-  name: "device_aware_permission_apis"
+  name: "device_aware_permission_apis_enabled"
   is_fixed_read_only: true
   namespace: "permissions"
   description: "enable device aware permission APIs"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8b5995a..9c27f19 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -608,6 +608,23 @@
 
     /**
      * Activity Action: Show settings to allow configuration of
+     * {@link Manifest.permission#MEDIA_ROUTING_CONTROL} permission.
+     *
+     * Input: Optionally, the Intent's data URI can specify the application package name to
+     * directly invoke the management GUI specific to the package name. For example
+     * "package:com.my.app". However, modifying this permission setting for any package is allowed
+     * only when that package holds an appropriate companion device profile such as
+     * {@link android.companion.AssociationRequest#DEVICE_PROFILE_WATCH}.
+     * <p>
+     * Output: Nothing.
+     */
+    @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control")
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL =
+            "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
+
+    /**
+     * Activity Action: Show settings to allow configuration of
      * {@link Manifest.permission#RUN_USER_INITIATED_JOBS} permission
      *
      * Input: Optionally, the Intent's data URI can specify the application package name to
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
new file mode 100644
index 0000000..91a713e
--- /dev/null
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.service.dreams"
+
+flag {
+  name: "dream_overlay_host"
+  namespace: "communal"
+  description: "This flag enables using a host to handle displaying a dream's overlay rather than "
+      "relying on the dream's window"
+  bug: "291990564"
+}
diff --git a/core/java/android/service/notification/DeviceEffectsApplier.java b/core/java/android/service/notification/DeviceEffectsApplier.java
index 234ff4d..5194cdd 100644
--- a/core/java/android/service/notification/DeviceEffectsApplier.java
+++ b/core/java/android/service/notification/DeviceEffectsApplier.java
@@ -16,6 +16,8 @@
 
 package android.service.notification;
 
+import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
+
 /**
  * Responsible for making any service calls needed to apply the set of {@link ZenDeviceEffects} that
  * make sense for the current platform.
@@ -33,6 +35,13 @@
      *
      * <p>This will be called whenever the set of consolidated effects changes (normally through
      * the activation or deactivation of zen rules).
+     *
+     * @param effects The effects that should be active and inactive.
+     * @param source The origin of the change. Because the application of specific effects can be
+     *               disruptive (e.g. lead to Activity recreation), that operation can in some
+     *               cases be deferred (e.g. until screen off). However, if the effects are
+     *               changing as a result of an explicit user action, then it makes sense to
+     *               apply them immediately regardless.
      */
-    void apply(ZenDeviceEffects effects);
+    void apply(ZenDeviceEffects effects, @ConfigChangeOrigin int source);
 }
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index f6128ea..a5b087c 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -26,6 +26,7 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
 
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
@@ -61,6 +62,8 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
@@ -79,7 +82,66 @@
  * @hide
  */
 public class ZenModeConfig implements Parcelable {
-    private static String TAG = "ZenModeConfig";
+    private static final String TAG = "ZenModeConfig";
+
+    /**
+     * The {@link ZenModeConfig} is being updated because of an unknown reason.
+     */
+    public static final int UPDATE_ORIGIN_UNKNOWN = 0;
+
+    /**
+     * The {@link ZenModeConfig} is being updated because of system initialization (i.e. load from
+     * storage, on device boot).
+     */
+    public static final int UPDATE_ORIGIN_INIT = 1;
+
+    /** The {@link ZenModeConfig} is being updated (replaced) because of a user switch or unlock. */
+    public static final int UPDATE_ORIGIN_INIT_USER = 2;
+
+    /** The {@link ZenModeConfig} is being updated because of a user action, for example:
+     * <ul>
+     *     <li>{@link NotificationManager#setAutomaticZenRuleState} with a
+     *     {@link Condition#source} equal to {@link Condition#SOURCE_USER_ACTION}.</li>
+     *     <li>Adding, updating, or removing a rule from Settings.</li>
+     *     <li>Directly activating or deactivating/snoozing a rule through some UI affordance (e.g.
+     *     Quick Settings).</li>
+     * </ul>
+     */
+    public static final int UPDATE_ORIGIN_USER = 3;
+
+    /**
+     * The {@link ZenModeConfig} is being "independently" updated by an app, and not as a result of
+     * a user's action inside that app (for example, activating an {@link AutomaticZenRule} based on
+     * a previously set schedule).
+     */
+    public static final int UPDATE_ORIGIN_APP = 4;
+
+    /**
+     * The {@link ZenModeConfig} is being updated by the System or SystemUI. Note that this only
+     * includes cases where the call is coming from the System/SystemUI but the change is not due to
+     * a user action (e.g. automatically activating a schedule-based rule). If the change is a
+     * result of a user action (e.g. activating a rule by tapping on its QS tile) then
+     * {@link #UPDATE_ORIGIN_USER} is used instead.
+     */
+    public static final int UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI = 5;
+
+    /**
+     * The {@link ZenModeConfig} is being updated (replaced) because the user's DND configuration
+     * is being restored from a backup.
+     */
+    public static final int UPDATE_ORIGIN_RESTORE_BACKUP = 6;
+
+    @IntDef(prefix = { "UPDATE_ORIGIN_" }, value = {
+            UPDATE_ORIGIN_UNKNOWN,
+            UPDATE_ORIGIN_INIT,
+            UPDATE_ORIGIN_INIT_USER,
+            UPDATE_ORIGIN_USER,
+            UPDATE_ORIGIN_APP,
+            UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+            UPDATE_ORIGIN_RESTORE_BACKUP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ConfigChangeOrigin {}
 
     public static final int SOURCE_ANYONE = Policy.PRIORITY_SENDERS_ANY;
     public static final int SOURCE_CONTACT = Policy.PRIORITY_SENDERS_CONTACTS;
diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java
index d184b1e..76b076b 100644
--- a/core/java/android/service/voice/VisualQueryDetectionService.java
+++ b/core/java/android/service/voice/VisualQueryDetectionService.java
@@ -338,16 +338,24 @@
 
     /**
      * Overrides {@link Context#openFileInput} to read files with the given file names under the
-     * internal app storage of the {@link VoiceInteractionService}, i.e., only files stored in
-     * {@link Context#getFilesDir()} can be opened.
+     * internal app storage of the {@link VoiceInteractionService}, i.e., the input file path would
+     * be added with {@link Context#getFilesDir()} as prefix.
+     *
+     * @param filename Relative path of a file under {@link Context#getFilesDir()}.
+     * @throws FileNotFoundException if the file does not exist or cannot be open.
      */
     @Override
-    public @Nullable FileInputStream openFileInput(@NonNull String filename) throws
+    public @NonNull FileInputStream openFileInput(@NonNull String filename) throws
             FileNotFoundException {
         try {
             AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>();
+            assert mDetectorSessionStorageService != null;
             mDetectorSessionStorageService.openFile(filename, future);
             ParcelFileDescriptor pfd = future.get();
+            if (pfd == null) {
+                throw new FileNotFoundException(
+                        "File does not exist. Unable to open " + filename + ".");
+            }
             return new FileInputStream(pfd.getFileDescriptor());
         } catch (RemoteException | ExecutionException | InterruptedException e) {
             Log.w(TAG, "Cannot open file due to remote service failure");
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index 91de894..b7d9705 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -447,12 +447,12 @@
         public void onOpenFile(String filename, AndroidFuture future) throws RemoteException {
             Slog.v(TAG, "BinderCallback#onOpenFile " + filename);
             Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
-                Slog.v(TAG, "onOpenFile: " + filename);
+                Slog.v(TAG, "onOpenFile: " + filename + "under internal app storage.");
                 File f = new File(mContext.getFilesDir(), filename);
                 ParcelFileDescriptor pfd = null;
                 try {
-                    Slog.d(TAG, "opened a file with ParcelFileDescriptor.");
                     pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
+                    Slog.d(TAG, "Successfully opened a file with ParcelFileDescriptor.");
                 } catch (FileNotFoundException e) {
                     Slog.e(TAG, "Cannot open file. No ParcelFileDescriptor returned.");
                 } finally {
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index fd5517d..f28574e 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -27,9 +27,6 @@
 
 import com.android.window.flags.Flags;
 
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
 /**
  * Provides an interface to the root-Surface of a View Hierarchy or Window. This
  * is used in combination with the {@link android.view.SurfaceControl} API to enable
@@ -197,42 +194,6 @@
     }
 
     /**
-     * Add a trusted presentation listener on the SurfaceControl associated with this window.
-     *
-     * @param t          Transaction that the trusted presentation listener is added on. This should
-     *                   be applied by the caller.
-     * @param thresholds The {@link SurfaceControl.TrustedPresentationThresholds} that will specify
-     *                   when the to invoke the callback.
-     * @param executor   The {@link Executor} where the callback will be invoked on.
-     * @param listener   The {@link Consumer} that will receive the callbacks when entered or
-     *                   exited the threshold.
-     *
-     * @see SurfaceControl.Transaction#setTrustedPresentationCallback(SurfaceControl,
-     * SurfaceControl.TrustedPresentationThresholds, Executor, Consumer)
-     *
-     * @hide b/287076178 un-hide with API bump
-     */
-    default void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
-            @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
-            @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
-    }
-
-    /**
-     * Remove a trusted presentation listener on the SurfaceControl associated with this window.
-     *
-     * @param t          Transaction that the trusted presentation listener removed on. This should
-     *                   be applied by the caller.
-     * @param listener   The {@link Consumer} that was previously registered with
-     *                   addTrustedPresentationCallback that should be removed.
-     *
-     * @see SurfaceControl.Transaction#clearTrustedPresentationCallback(SurfaceControl)
-     * @hide b/287076178 un-hide with API bump
-     */
-    default void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
-            @NonNull Consumer<Boolean> listener) {
-    }
-
-    /**
      * Transfer the currently in progress touch gesture from the host to the requested
      * {@link SurfaceControlViewHost.SurfacePackage}. This requires that the
      * SurfaceControlViewHost was created with the current host's inputToken.
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 07dd882..1908c64c 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -276,15 +276,14 @@
     /**
      * Display flag: Indicates that the display should show system decorations.
      * <p>
-     * This flag identifies secondary displays that should show system decorations, such as status
-     * bar, navigation bar, home activity or IME.
+     * This flag identifies secondary displays that should show system decorations, such as
+     * navigation bar, home activity or wallpaper.
      * </p>
      * <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p>
      *
      * @see #getFlags()
      * @hide
      */
-    // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors
     public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 6;
 
     /**
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 17bbee6d0..36b74e3 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -73,6 +73,8 @@
 import android.window.ITaskFpsCallback;
 import android.window.ScreenCapture;
 import android.window.WindowContextInfo;
+import android.window.ITrustedPresentationListener;
+import android.window.TrustedPresentationThresholds;
 
 /**
  * System private interface to the window manager.
@@ -1075,4 +1077,10 @@
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MONITOR_INPUT)")
     void unregisterDecorViewGestureListener(IDecorViewGestureListener listener, int displayId);
+
+    void registerTrustedPresentationListener(in IBinder window, in ITrustedPresentationListener listener,
+            in TrustedPresentationThresholds thresholds, int id);
+
+
+    void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7134529..5cbb42e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -826,11 +826,19 @@
      * The resolved pointer icon type requested by this window.
      * A null value indicates the resolved pointer icon has not yet been calculated.
      */
+    // TODO(b/293587049): Remove pointer icon tracking by type when refactor is complete.
     @Nullable
     private Integer mPointerIconType = null;
     private PointerIcon mCustomPointerIcon = null;
 
     /**
+     * The resolved pointer icon requested by this window.
+     * A null value indicates the resolved pointer icon has not yet been calculated.
+     */
+    @Nullable
+    private PointerIcon mResolvedPointerIcon = null;
+
+    /**
      * see {@link #playSoundEffect(int)}
      */
     AudioManager mAudioManager;
@@ -7410,12 +7418,14 @@
                 // Other apps or the window manager may change the icon type outside of
                 // this app, therefore the icon type has to be reset on enter/exit event.
                 mPointerIconType = null;
+                mResolvedPointerIcon = null;
             }
 
             if (action != MotionEvent.ACTION_HOVER_EXIT) {
                 // Resolve the pointer icon
                 if (!updatePointerIcon(event) && action == MotionEvent.ACTION_HOVER_MOVE) {
                     mPointerIconType = null;
+                    mResolvedPointerIcon = null;
                 }
             }
 
@@ -7470,6 +7480,7 @@
 
     private void resetPointerIcon(MotionEvent event) {
         mPointerIconType = null;
+        mResolvedPointerIcon = null;
         updatePointerIcon(event);
     }
 
@@ -7507,6 +7518,21 @@
             pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);
         }
 
+        if (enablePointerChoreographer()) {
+            if (pointerIcon == null) {
+                pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED);
+            }
+            if (Objects.equals(mResolvedPointerIcon, pointerIcon)) {
+                return true;
+            }
+            mResolvedPointerIcon = pointerIcon;
+
+            InputManagerGlobal.getInstance()
+                    .setPointerIcon(pointerIcon, event.getDisplayId(),
+                            event.getDeviceId(), event.getPointerId(0), getInputToken());
+            return true;
+        }
+
         final int pointerType = (pointerIcon != null) ?
                 pointerIcon.getType() : PointerIcon.TYPE_NOT_SPECIFIED;
 
@@ -7514,34 +7540,18 @@
             mPointerIconType = pointerType;
             mCustomPointerIcon = null;
             if (mPointerIconType != PointerIcon.TYPE_CUSTOM) {
-                if (enablePointerChoreographer()) {
-                    InputManagerGlobal
-                            .getInstance()
-                            .setPointerIcon(PointerIcon.getSystemIcon(mContext, pointerType),
-                                    event.getDisplayId(), event.getDeviceId(),
-                                    event.getPointerId(pointerIndex), getInputToken());
-                } else {
-                    InputManagerGlobal
-                            .getInstance()
-                            .setPointerIconType(pointerType);
-                }
+                InputManagerGlobal
+                        .getInstance()
+                        .setPointerIconType(pointerType);
                 return true;
             }
         }
         if (mPointerIconType == PointerIcon.TYPE_CUSTOM &&
                 !pointerIcon.equals(mCustomPointerIcon)) {
             mCustomPointerIcon = pointerIcon;
-            if (enablePointerChoreographer()) {
-                InputManagerGlobal
-                        .getInstance()
-                        .setPointerIcon(mCustomPointerIcon,
-                                event.getDisplayId(), event.getDeviceId(),
-                                event.getPointerId(pointerIndex), getInputToken());
-            } else {
-                InputManagerGlobal
-                        .getInstance()
-                        .setCustomPointerIcon(mCustomPointerIcon);
-            }
+            InputManagerGlobal
+                    .getInstance()
+                    .setCustomPointerIcon(mCustomPointerIcon);
         }
         return true;
     }
@@ -12016,18 +12026,6 @@
         scheduleTraversals();
     }
 
-    @Override
-    public void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
-            @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
-            @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
-        t.setTrustedPresentationCallback(getSurfaceControl(), thresholds, executor, listener);
-    }
-
-    @Override
-    public void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
-            @NonNull Consumer<Boolean> listener) {
-        t.clearTrustedPresentationCallback(getSurfaceControl());
-    }
 
     private void logAndTrace(String msg) {
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 046ea77..c7e1807 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -122,7 +122,11 @@
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.window.ITrustedPresentationListener;
 import android.window.TaskFpsCallback;
+import android.window.TrustedPresentationThresholds;
+
+import com.android.window.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -656,26 +660,35 @@
 
     /**
      * Display IME Policy: The IME should appear on the local display.
+     *
      * @hide
      */
-    @TestApi
+    @SuppressLint("UnflaggedApi")  // promoting from @TestApi.
+    @SystemApi
     int DISPLAY_IME_POLICY_LOCAL = 0;
 
     /**
-     * Display IME Policy: The IME should appear on the fallback display.
+     * Display IME Policy: The IME should appear on a fallback display.
+     *
+     * <p>The fallback display is always {@link Display#DEFAULT_DISPLAY}.</p>
+     *
      * @hide
      */
-    @TestApi
+    @SuppressLint("UnflaggedApi")  // promoting from @TestApi.
+    @SystemApi
     int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1;
 
     /**
      * Display IME Policy: The IME should be hidden.
      *
-     * Setting this policy will prevent the IME from making a connection. This
-     * will prevent any IME from receiving metadata about input.
+     * <p>Setting this policy will prevent the IME from making a connection. This
+     * will prevent any IME from receiving metadata about input and this display will effectively
+     * have no IME.</p>
+     *
      * @hide
      */
-    @TestApi
+    @SuppressLint("UnflaggedApi")  // promoting from @TestApi.
+    @SystemApi
     int DISPLAY_IME_POLICY_HIDE = 2;
 
     /**
@@ -3251,6 +3264,13 @@
         public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 1 << 24;
 
         /**
+         * Flag to indicate that the window consumes the insets of {@link Type#ime()}. This makes
+         * windows below this window unable to receive visible IME insets.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_CONSUME_IME_INSETS = 1 << 25;
+
+        /**
          * Flag to indicate that the window is controlling the appearance of system bars. So we
          * don't need to adjust it by reading its system UI flags for compatibility.
          * @hide
@@ -3334,6 +3354,7 @@
                 PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
                 PRIVATE_FLAG_NOT_MAGNIFIABLE,
                 PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
+                PRIVATE_FLAG_CONSUME_IME_INSETS,
                 PRIVATE_FLAG_APPEARANCE_CONTROLLED,
                 PRIVATE_FLAG_BEHAVIOR_CONTROLLED,
                 PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
@@ -3432,6 +3453,10 @@
                         equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
                         name = "COLOR_SPACE_AGNOSTIC"),
                 @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_CONSUME_IME_INSETS,
+                        equals = PRIVATE_FLAG_CONSUME_IME_INSETS,
+                        name = "CONSUME_IME_INSETS"),
+                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
                         equals = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
                         name = "APPEARANCE_CONTROLLED"),
@@ -3458,7 +3483,7 @@
                 @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY,
                         equals = PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY,
-                        name = "PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY")
+                        name = "SYSTEM_APPLICATION_OVERLAY")
         })
         @PrivateFlags
         @TestApi
@@ -5884,4 +5909,34 @@
     default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * Add a trusted presentation listener associated with a window.
+     *
+     * <p> If this listener is already registered then the window and thresholds will be updated.
+     *
+     * @param window     The Window to add the trusted presentation listener for
+     * @param thresholds The {@link TrustedPresentationThresholds} that will specify
+     *                   when the to invoke the callback.
+     * @param executor   The {@link Executor} where the callback will be invoked on.
+     * @param listener   The {@link Consumer} that will receive the callbacks
+     *                  when entered or exited trusted presentation per the thresholds.
+     */
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    default void registerTrustedPresentationListener(@NonNull IBinder window,
+            @NonNull TrustedPresentationThresholds thresholds,  @NonNull Executor executor,
+            @NonNull Consumer<Boolean> listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Removes a presentation listener associated with a window. If the listener was not previously
+     * registered, the call will be a noop.
+     *
+     * @see WindowManager#registerTrustedPresentationListener(IBinder, TrustedPresentationThresholds, Executor, Consumer)
+     */
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    default void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 214f1ec..f1e4061 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -30,9 +30,13 @@
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.util.AndroidRuntimeException;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pair;
 import android.view.inputmethod.InputMethodManager;
+import android.window.ITrustedPresentationListener;
+import android.window.TrustedPresentationThresholds;
 
 import com.android.internal.util.FastPrintWriter;
 
@@ -43,6 +47,7 @@
 import java.util.ArrayList;
 import java.util.WeakHashMap;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
 /**
@@ -143,6 +148,9 @@
 
     private Runnable mSystemPropertyUpdater;
 
+    private final TrustedPresentationListener mTrustedPresentationListener =
+            new TrustedPresentationListener();
+
     private WindowManagerGlobal() {
     }
 
@@ -324,7 +332,7 @@
             final Context context = view.getContext();
             if (context != null
                     && (context.getApplicationInfo().flags
-                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
+                    & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                 wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
             }
         }
@@ -482,7 +490,7 @@
                     if (who != null) {
                         WindowLeaked leak = new WindowLeaked(
                                 what + " " + who + " has leaked window "
-                                + root.getView() + " that was originally added here");
+                                        + root.getView() + " that was originally added here");
                         leak.setStackTrace(root.getLocation().getStackTrace());
                         Log.e(TAG, "", leak);
                     }
@@ -790,6 +798,87 @@
         }
     }
 
+    public void registerTrustedPresentationListener(@NonNull IBinder window,
+            @NonNull TrustedPresentationThresholds thresholds, Executor executor,
+            @NonNull Consumer<Boolean> listener) {
+        mTrustedPresentationListener.addListener(window, thresholds, listener, executor);
+    }
+
+    public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
+        mTrustedPresentationListener.removeListener(listener);
+    }
+
+    private final class TrustedPresentationListener extends
+            ITrustedPresentationListener.Stub {
+        private static int sId = 0;
+        private final ArrayMap<Consumer<Boolean>, Pair<Integer, Executor>> mListeners =
+                new ArrayMap<>();
+
+        private final Object mTplLock = new Object();
+
+        private void addListener(IBinder window, TrustedPresentationThresholds thresholds,
+                Consumer<Boolean> listener, Executor executor) {
+            synchronized (mTplLock) {
+                if (mListeners.containsKey(listener)) {
+                    Log.i(TAG, "Updating listener " + listener + " thresholds to " + thresholds);
+                    removeListener(listener);
+                }
+                int id = sId++;
+                mListeners.put(listener, new Pair<>(id, executor));
+                try {
+                    WindowManagerGlobal.getWindowManagerService()
+                            .registerTrustedPresentationListener(window, this, thresholds, id);
+                } catch (RemoteException e) {
+                    e.rethrowAsRuntimeException();
+                }
+            }
+        }
+
+        private void removeListener(Consumer<Boolean> listener) {
+            synchronized (mTplLock) {
+                var removedListener = mListeners.remove(listener);
+                if (removedListener == null) {
+                    Log.i(TAG, "listener " + listener + " does not exist.");
+                    return;
+                }
+
+                try {
+                    WindowManagerGlobal.getWindowManagerService()
+                            .unregisterTrustedPresentationListener(this, removedListener.first);
+                } catch (RemoteException e) {
+                    e.rethrowAsRuntimeException();
+                }
+            }
+        }
+
+        @Override
+        public void onTrustedPresentationChanged(int[] inTrustedStateListenerIds,
+                int[] outOfTrustedStateListenerIds) {
+            ArrayList<Runnable> firedListeners = new ArrayList<>();
+            synchronized (mTplLock) {
+                mListeners.forEach((listener, idExecutorPair) -> {
+                    final var listenerId =  idExecutorPair.first;
+                    final var executor = idExecutorPair.second;
+                    for (int id : inTrustedStateListenerIds) {
+                        if (listenerId == id) {
+                            firedListeners.add(() -> executor.execute(
+                                    () -> listener.accept(/*presentationState*/true)));
+                        }
+                    }
+                    for (int id : outOfTrustedStateListenerIds) {
+                        if (listenerId == id) {
+                            firedListeners.add(() -> executor.execute(
+                                    () -> listener.accept(/*presentationState*/false)));
+                        }
+                    }
+                });
+            }
+            for (int i = 0; i < firedListeners.size(); i++) {
+                firedListeners.get(i).run();
+            }
+        }
+    }
+
     /** @hide */
     public void addWindowlessRoot(ViewRootImpl impl) {
         synchronized (mLock) {
@@ -801,7 +890,7 @@
     public void removeWindowlessRoot(ViewRootImpl impl) {
         synchronized (mLock) {
             mWindowlessRoots.remove(impl);
-	}
+        }
     }
 
     public void setRecentsAppBehindSystemBars(boolean behindSystemBars) {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index d7b74b3..b4b1fde 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -37,6 +37,7 @@
 import android.util.Log;
 import android.window.ITaskFpsCallback;
 import android.window.TaskFpsCallback;
+import android.window.TrustedPresentationThresholds;
 import android.window.WindowContext;
 import android.window.WindowMetricsController;
 import android.window.WindowProvider;
@@ -508,4 +509,17 @@
         }
         return false;
     }
+
+    @Override
+    public void registerTrustedPresentationListener(@NonNull IBinder window,
+            @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor,
+            @NonNull Consumer<Boolean> listener) {
+        mGlobal.registerTrustedPresentationListener(window, thresholds, executor, listener);
+    }
+
+    @Override
+    public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
+        mGlobal.unregisterTrustedPresentationListener(listener);
+
+    }
 }
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index fab8c77..1b7d57b 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -20,8 +20,8 @@
 import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
 
 import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_HIDE_ANIMATION;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_SHOW_ANIMATION;
+import static com.android.internal.jank.Cuj.CUJ_IME_INSETS_HIDE_ANIMATION;
+import static com.android.internal.jank.Cuj.CUJ_IME_INSETS_SHOW_ANIMATION;
 import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN;
 import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN;
 
@@ -758,7 +758,7 @@
          * A helper method to translate animation type to CUJ type for IME animations.
          *
          * @param animType the animation type.
-         * @return the integer in {@link com.android.internal.jank.InteractionJankMonitor.CujType},
+         * @return the integer in {@link com.android.internal.jank.Cuj.CujType},
          * or {@code -1} if the animation type is not supported for tracking yet.
          */
         private static int getImeInsetsCujFromAnimation(@AnimationType int animType) {
diff --git a/core/java/android/window/ITrustedPresentationListener.aidl b/core/java/android/window/ITrustedPresentationListener.aidl
new file mode 100644
index 0000000..b33128a
--- /dev/null
+++ b/core/java/android/window/ITrustedPresentationListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.window;
+
+/**
+ * @hide
+ */
+oneway interface ITrustedPresentationListener {
+    void onTrustedPresentationChanged(in int[] enteredTrustedStateIds, in int[] exitedTrustedStateIds);
+}
\ No newline at end of file
diff --git a/core/java/android/window/TrustedPresentationListener.java b/core/java/android/window/TrustedPresentationListener.java
new file mode 100644
index 0000000..02fd6d9
--- /dev/null
+++ b/core/java/android/window/TrustedPresentationListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 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.window;
+
+/**
+ * @hide
+ */
+public interface TrustedPresentationListener {
+
+    void onTrustedPresentationChanged(boolean inTrustedPresentationState);
+
+}
diff --git a/core/java/android/window/TrustedPresentationThresholds.aidl b/core/java/android/window/TrustedPresentationThresholds.aidl
new file mode 100644
index 0000000..d7088bf
--- /dev/null
+++ b/core/java/android/window/TrustedPresentationThresholds.aidl
@@ -0,0 +1,3 @@
+package android.window;
+
+parcelable TrustedPresentationThresholds;
diff --git a/core/java/android/window/TrustedPresentationThresholds.java b/core/java/android/window/TrustedPresentationThresholds.java
new file mode 100644
index 0000000..90f8834
--- /dev/null
+++ b/core/java/android/window/TrustedPresentationThresholds.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 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.window;
+
+import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
+
+import com.android.window.flags.Flags;
+
+/**
+ * Threshold values that are sent with
+ * {@link android.view.WindowManager#registerTrustedPresentationListener(IBinder,
+ * TrustedPresentationThresholds, Executor, Consumer)}
+ */
+@FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+public final class TrustedPresentationThresholds implements Parcelable {
+    /**
+     * The min alpha the {@link SurfaceControl} is required to have to be considered inside the
+     * threshold.
+     */
+    @FloatRange(from = 0f, fromInclusive = false, to = 1f)
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    @SuppressLint("InternalField") // simple data class
+    public final float minAlpha;
+
+    /**
+     * The min fraction of the SurfaceControl that was presented to the user to be considered
+     * inside the threshold.
+     */
+    @FloatRange(from = 0f, fromInclusive = false, to = 1f)
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    @SuppressLint("InternalField") // simple data class
+    public final float minFractionRendered;
+
+    /**
+     * The time in milliseconds required for the {@link SurfaceControl} to be in the threshold.
+     */
+    @IntRange(from = 1)
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    @SuppressLint("InternalField") // simple data class
+    public final int stabilityRequirementMs;
+
+    private void checkValid() {
+        if (minAlpha <= 0 || minFractionRendered <= 0 || stabilityRequirementMs < 1) {
+            throw new IllegalArgumentException(
+                    "TrustedPresentationThresholds values are invalid");
+        }
+    }
+
+    /**
+     * Creates a new TrustedPresentationThresholds.
+     *
+     * @param minAlpha               The min alpha the {@link SurfaceControl} is required to
+     *                               have to be considered inside the
+     *                               threshold.
+     * @param minFractionRendered    The min fraction of the SurfaceControl that was presented
+     *                               to the user to be considered
+     *                               inside the threshold.
+     * @param stabilityRequirementMs The time in milliseconds required for the
+     *                               {@link SurfaceControl} to be in the threshold.
+     */
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    public TrustedPresentationThresholds(
+            @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minAlpha,
+            @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minFractionRendered,
+            @IntRange(from = 1) int stabilityRequirementMs) {
+        this.minAlpha = minAlpha;
+        this.minFractionRendered = minFractionRendered;
+        this.stabilityRequirementMs = stabilityRequirementMs;
+        checkValid();
+    }
+
+    @Override
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    public String toString() {
+        return "TrustedPresentationThresholds { "
+                + "minAlpha = " + minAlpha + ", "
+                + "minFractionRendered = " + minFractionRendered + ", "
+                + "stabilityRequirementMs = " + stabilityRequirementMs
+                + " }";
+    }
+
+    @Override
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeFloat(minAlpha);
+        dest.writeFloat(minFractionRendered);
+        dest.writeInt(stabilityRequirementMs);
+    }
+
+    @Override
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @hide
+     */
+    TrustedPresentationThresholds(@NonNull Parcel in) {
+        minAlpha = in.readFloat();
+        minFractionRendered = in.readFloat();
+        stabilityRequirementMs = in.readInt();
+
+        checkValid();
+    }
+
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    public static final @NonNull Creator<TrustedPresentationThresholds> CREATOR =
+            new Creator<TrustedPresentationThresholds>() {
+                @Override
+                @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+                public TrustedPresentationThresholds[] newArray(int size) {
+                    return new TrustedPresentationThresholds[size];
+                }
+
+                @Override
+                @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+                public TrustedPresentationThresholds createFromParcel(@NonNull Parcel in) {
+                    return new TrustedPresentationThresholds(in);
+                }
+            };
+}
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 29932f3..56df493 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -56,3 +56,11 @@
     is_fixed_read_only: true
     bug: "308662081"
 }
+
+flag {
+    namespace: "window_surfaces"
+    name: "trusted_presentation_listener_for_window"
+    description: "Enable trustedPresentationListener on windows public API"
+    is_fixed_read_only: true
+    bug: "278027319"
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
new file mode 100644
index 0000000..f460233
--- /dev/null
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -0,0 +1,503 @@
+/*
+ * 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.internal.jank;
+
+import android.annotation.IntDef;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/** @hide */
+public class Cuj {
+    @VisibleForTesting
+    public static final int MAX_LENGTH_OF_CUJ_NAME = 80;
+
+    // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
+    public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
+    public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2;
+    public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3;
+    public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4;
+    public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = 5;
+    public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = 6;
+    public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS = 7;
+    public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON = 8;
+    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 9;
+    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 10;
+    public static final int CUJ_LAUNCHER_QUICK_SWITCH = 11;
+    public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = 12;
+    public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = 13;
+    public static final int CUJ_NOTIFICATION_ADD = 14;
+    public static final int CUJ_NOTIFICATION_REMOVE = 15;
+    public static final int CUJ_NOTIFICATION_APP_START = 16;
+    public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = 17;
+    public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = 18;
+    public static final int CUJ_LOCKSCREEN_PIN_APPEAR = 19;
+    public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = 20;
+    public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = 21;
+    public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = 22;
+    public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = 23;
+    public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = 24;
+    public static final int CUJ_LAUNCHER_OPEN_ALL_APPS = 25;
+    public static final int CUJ_LAUNCHER_ALL_APPS_SCROLL = 26;
+    public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET = 27;
+    public static final int CUJ_SETTINGS_PAGE_SCROLL = 28;
+    public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = 29;
+    public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = 30;
+    public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = 31;
+    public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32;
+    public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33;
+    public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34;
+    public static final int CUJ_PIP_TRANSITION = 35;
+    public static final int CUJ_WALLPAPER_TRANSITION = 36;
+    public static final int CUJ_USER_SWITCH = 37;
+    public static final int CUJ_SPLASHSCREEN_AVD = 38;
+    public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39;
+    public static final int CUJ_SCREEN_OFF = 40;
+    public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41;
+    public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42;
+    public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43;
+    public static final int CUJ_UNFOLD_ANIM = 44;
+    public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = 45;
+    public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = 46;
+    public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = 47;
+    public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = 48;
+    public static final int CUJ_SPLIT_SCREEN_ENTER = 49;
+    public static final int CUJ_SPLIT_SCREEN_EXIT = 50;
+    public static final int CUJ_LOCKSCREEN_LAUNCH_CAMERA = 51; // reserved.
+    public static final int CUJ_SPLIT_SCREEN_RESIZE = 52;
+    public static final int CUJ_SETTINGS_SLIDER = 53;
+    public static final int CUJ_TAKE_SCREENSHOT = 54;
+    public static final int CUJ_VOLUME_CONTROL = 55;
+    public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = 56;
+    public static final int CUJ_SETTINGS_TOGGLE = 57;
+    public static final int CUJ_SHADE_DIALOG_OPEN = 58;
+    public static final int CUJ_USER_DIALOG_OPEN = 59;
+    public static final int CUJ_TASKBAR_EXPAND = 60;
+    public static final int CUJ_TASKBAR_COLLAPSE = 61;
+    public static final int CUJ_SHADE_CLEAR_ALL = 62;
+    public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63;
+    public static final int CUJ_LOCKSCREEN_OCCLUSION = 64;
+    public static final int CUJ_RECENTS_SCROLLING = 65;
+    public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
+    public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
+    public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
+    public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
+    public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71;
+    // 72 - 77 are reserved for b/281564325.
+
+    /**
+     * In some cases when we do not have any end-target, we play a simple slide-down animation.
+     * eg: Open an app from Overview/Task switcher such that there is no home-screen icon.
+     * eg: Exit the app using back gesture.
+     */
+    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78;
+    public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80;
+    public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81;
+
+    public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82;
+
+    public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83;
+
+    public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84;
+    public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
+    public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
+
+    // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
+    @VisibleForTesting
+    static final int LAST_CUJ = CUJ_PREDICTIVE_BACK_HOME;
+
+    /** @hide */
+    @IntDef({
+            CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+            CUJ_NOTIFICATION_SHADE_SCROLL_FLING,
+            CUJ_NOTIFICATION_SHADE_ROW_EXPAND,
+            CUJ_NOTIFICATION_SHADE_ROW_SWIPE,
+            CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+            CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE,
+            CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS,
+            CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON,
+            CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
+            CUJ_LAUNCHER_APP_CLOSE_TO_PIP,
+            CUJ_LAUNCHER_QUICK_SWITCH,
+            CUJ_NOTIFICATION_HEADS_UP_APPEAR,
+            CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR,
+            CUJ_NOTIFICATION_ADD,
+            CUJ_NOTIFICATION_REMOVE,
+            CUJ_NOTIFICATION_APP_START,
+            CUJ_LOCKSCREEN_PASSWORD_APPEAR,
+            CUJ_LOCKSCREEN_PATTERN_APPEAR,
+            CUJ_LOCKSCREEN_PIN_APPEAR,
+            CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR,
+            CUJ_LOCKSCREEN_PATTERN_DISAPPEAR,
+            CUJ_LOCKSCREEN_PIN_DISAPPEAR,
+            CUJ_LOCKSCREEN_TRANSITION_FROM_AOD,
+            CUJ_LOCKSCREEN_TRANSITION_TO_AOD,
+            CUJ_LAUNCHER_OPEN_ALL_APPS,
+            CUJ_LAUNCHER_ALL_APPS_SCROLL,
+            CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET,
+            CUJ_SETTINGS_PAGE_SCROLL,
+            CUJ_LOCKSCREEN_UNLOCK_ANIMATION,
+            CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON,
+            CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER,
+            CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+            CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
+            CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+            CUJ_PIP_TRANSITION,
+            CUJ_WALLPAPER_TRANSITION,
+            CUJ_USER_SWITCH,
+            CUJ_SPLASHSCREEN_AVD,
+            CUJ_SPLASHSCREEN_EXIT_ANIM,
+            CUJ_SCREEN_OFF,
+            CUJ_SCREEN_OFF_SHOW_AOD,
+            CUJ_ONE_HANDED_ENTER_TRANSITION,
+            CUJ_ONE_HANDED_EXIT_TRANSITION,
+            CUJ_UNFOLD_ANIM,
+            CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS,
+            CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS,
+            CUJ_SUW_LOADING_TO_NEXT_FLOW,
+            CUJ_SUW_LOADING_SCREEN_FOR_STATUS,
+            CUJ_SPLIT_SCREEN_ENTER,
+            CUJ_SPLIT_SCREEN_EXIT,
+            CUJ_LOCKSCREEN_LAUNCH_CAMERA,
+            CUJ_SPLIT_SCREEN_RESIZE,
+            CUJ_SETTINGS_SLIDER,
+            CUJ_TAKE_SCREENSHOT,
+            CUJ_VOLUME_CONTROL,
+            CUJ_BIOMETRIC_PROMPT_TRANSITION,
+            CUJ_SETTINGS_TOGGLE,
+            CUJ_SHADE_DIALOG_OPEN,
+            CUJ_USER_DIALOG_OPEN,
+            CUJ_TASKBAR_EXPAND,
+            CUJ_TASKBAR_COLLAPSE,
+            CUJ_SHADE_CLEAR_ALL,
+            CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
+            CUJ_LOCKSCREEN_OCCLUSION,
+            CUJ_RECENTS_SCROLLING,
+            CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
+            CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
+            CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
+            CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION,
+            CUJ_LAUNCHER_OPEN_SEARCH_RESULT,
+            CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK,
+            CUJ_IME_INSETS_SHOW_ANIMATION,
+            CUJ_IME_INSETS_HIDE_ANIMATION,
+            CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
+            CUJ_LAUNCHER_UNFOLD_ANIM,
+            CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
+            CUJ_PREDICTIVE_BACK_CROSS_TASK,
+            CUJ_PREDICTIVE_BACK_HOME,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CujType {
+    }
+
+    private static final int NO_STATSD_LOGGING = -1;
+
+    // Used to convert CujType to InteractionType enum value for statsd logging.
+    // Use NO_STATSD_LOGGING in case the measurement for a given CUJ should not be logged to statsd.
+    private static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = new int[LAST_CUJ + 1];
+    static {
+        Arrays.fill(CUJ_TO_STATSD_INTERACTION_TYPE, NO_STATSD_LOGGING);
+
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_SCROLL_FLING] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_EXPAND] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_PIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_QUICK_SWITCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_ADD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_REMOVE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_APP_START] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_FROM_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_TO_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_ALL_APPS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_ALL_APPS_SCROLL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_PAGE_SCROLL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_UNLOCK_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PIP_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_SWITCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_AVD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_EXIT_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF_SHOW_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_ENTER_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_EXIT_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_UNFOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_NEXT_FLOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_SCREEN_FOR_STATUS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_ENTER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_EXIT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_LAUNCH_CAMERA] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_RESIZE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_SLIDER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TAKE_SCREENSHOT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_VOLUME_CONTROL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BIOMETRIC_PROMPT_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_TOGGLE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_DIALOG_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_DIALOG_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_EXPAND] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_CLEAR_ALL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_OCCLUSION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_RECENTS_SCROLLING] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
+    }
+
+    private Cuj() {
+    }
+
+    /**
+     * A helper method to translate CUJ type to CUJ name.
+     *
+     * @param cujType the cuj type defined in this file
+     * @return the name of the cuj type
+     */
+    public static String getNameOfCuj(int cujType) {
+        // Please note:
+        // 1. The length of the returned string shouldn't exceed MAX_LENGTH_OF_CUJ_NAME.
+        // 2. The returned string should be the same with the name defined in atoms.proto.
+        switch (cujType) {
+            case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE:
+                return "NOTIFICATION_SHADE_EXPAND_COLLAPSE";
+            case CUJ_NOTIFICATION_SHADE_SCROLL_FLING:
+                return "NOTIFICATION_SHADE_SCROLL_FLING";
+            case CUJ_NOTIFICATION_SHADE_ROW_EXPAND:
+                return "NOTIFICATION_SHADE_ROW_EXPAND";
+            case CUJ_NOTIFICATION_SHADE_ROW_SWIPE:
+                return "NOTIFICATION_SHADE_ROW_SWIPE";
+            case CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE:
+                return "NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE";
+            case CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE:
+                return "NOTIFICATION_SHADE_QS_SCROLL_SWIPE";
+            case CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS:
+                return "LAUNCHER_APP_LAUNCH_FROM_RECENTS";
+            case CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON:
+                return "LAUNCHER_APP_LAUNCH_FROM_ICON";
+            case CUJ_LAUNCHER_APP_CLOSE_TO_HOME:
+                return "LAUNCHER_APP_CLOSE_TO_HOME";
+            case CUJ_LAUNCHER_APP_CLOSE_TO_PIP:
+                return "LAUNCHER_APP_CLOSE_TO_PIP";
+            case CUJ_LAUNCHER_QUICK_SWITCH:
+                return "LAUNCHER_QUICK_SWITCH";
+            case CUJ_NOTIFICATION_HEADS_UP_APPEAR:
+                return "NOTIFICATION_HEADS_UP_APPEAR";
+            case CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR:
+                return "NOTIFICATION_HEADS_UP_DISAPPEAR";
+            case CUJ_NOTIFICATION_ADD:
+                return "NOTIFICATION_ADD";
+            case CUJ_NOTIFICATION_REMOVE:
+                return "NOTIFICATION_REMOVE";
+            case CUJ_NOTIFICATION_APP_START:
+                return "NOTIFICATION_APP_START";
+            case CUJ_LOCKSCREEN_PASSWORD_APPEAR:
+                return "LOCKSCREEN_PASSWORD_APPEAR";
+            case CUJ_LOCKSCREEN_PATTERN_APPEAR:
+                return "LOCKSCREEN_PATTERN_APPEAR";
+            case CUJ_LOCKSCREEN_PIN_APPEAR:
+                return "LOCKSCREEN_PIN_APPEAR";
+            case CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR:
+                return "LOCKSCREEN_PASSWORD_DISAPPEAR";
+            case CUJ_LOCKSCREEN_PATTERN_DISAPPEAR:
+                return "LOCKSCREEN_PATTERN_DISAPPEAR";
+            case CUJ_LOCKSCREEN_PIN_DISAPPEAR:
+                return "LOCKSCREEN_PIN_DISAPPEAR";
+            case CUJ_LOCKSCREEN_TRANSITION_FROM_AOD:
+                return "LOCKSCREEN_TRANSITION_FROM_AOD";
+            case CUJ_LOCKSCREEN_TRANSITION_TO_AOD:
+                return "LOCKSCREEN_TRANSITION_TO_AOD";
+            case CUJ_LAUNCHER_OPEN_ALL_APPS :
+                return "LAUNCHER_OPEN_ALL_APPS";
+            case CUJ_LAUNCHER_ALL_APPS_SCROLL:
+                return "LAUNCHER_ALL_APPS_SCROLL";
+            case CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET:
+                return "LAUNCHER_APP_LAUNCH_FROM_WIDGET";
+            case CUJ_SETTINGS_PAGE_SCROLL:
+                return "SETTINGS_PAGE_SCROLL";
+            case CUJ_LOCKSCREEN_UNLOCK_ANIMATION:
+                return "LOCKSCREEN_UNLOCK_ANIMATION";
+            case CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON:
+                return "SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON";
+            case CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER:
+                return "SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER";
+            case CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE:
+                return "SHADE_APP_LAUNCH_FROM_QS_TILE";
+            case CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON:
+                return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON";
+            case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP:
+                return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP";
+            case CUJ_PIP_TRANSITION:
+                return "PIP_TRANSITION";
+            case CUJ_WALLPAPER_TRANSITION:
+                return "WALLPAPER_TRANSITION";
+            case CUJ_USER_SWITCH:
+                return "USER_SWITCH";
+            case CUJ_SPLASHSCREEN_AVD:
+                return "SPLASHSCREEN_AVD";
+            case CUJ_SPLASHSCREEN_EXIT_ANIM:
+                return "SPLASHSCREEN_EXIT_ANIM";
+            case CUJ_SCREEN_OFF:
+                return "SCREEN_OFF";
+            case CUJ_SCREEN_OFF_SHOW_AOD:
+                return "SCREEN_OFF_SHOW_AOD";
+            case CUJ_ONE_HANDED_ENTER_TRANSITION:
+                return "ONE_HANDED_ENTER_TRANSITION";
+            case CUJ_ONE_HANDED_EXIT_TRANSITION:
+                return "ONE_HANDED_EXIT_TRANSITION";
+            case CUJ_UNFOLD_ANIM:
+                return "UNFOLD_ANIM";
+            case CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS:
+                return "SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS";
+            case CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS:
+                return "SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS";
+            case CUJ_SUW_LOADING_TO_NEXT_FLOW:
+                return "SUW_LOADING_TO_NEXT_FLOW";
+            case CUJ_SUW_LOADING_SCREEN_FOR_STATUS:
+                return "SUW_LOADING_SCREEN_FOR_STATUS";
+            case CUJ_SPLIT_SCREEN_ENTER:
+                return "SPLIT_SCREEN_ENTER";
+            case CUJ_SPLIT_SCREEN_EXIT:
+                return "SPLIT_SCREEN_EXIT";
+            case CUJ_LOCKSCREEN_LAUNCH_CAMERA:
+                return "LOCKSCREEN_LAUNCH_CAMERA";
+            case CUJ_SPLIT_SCREEN_RESIZE:
+                return "SPLIT_SCREEN_RESIZE";
+            case CUJ_SETTINGS_SLIDER:
+                return "SETTINGS_SLIDER";
+            case CUJ_TAKE_SCREENSHOT:
+                return "TAKE_SCREENSHOT";
+            case CUJ_VOLUME_CONTROL:
+                return "VOLUME_CONTROL";
+            case CUJ_BIOMETRIC_PROMPT_TRANSITION:
+                return "BIOMETRIC_PROMPT_TRANSITION";
+            case CUJ_SETTINGS_TOGGLE:
+                return "SETTINGS_TOGGLE";
+            case CUJ_SHADE_DIALOG_OPEN:
+                return "SHADE_DIALOG_OPEN";
+            case CUJ_USER_DIALOG_OPEN:
+                return "USER_DIALOG_OPEN";
+            case CUJ_TASKBAR_EXPAND:
+                return "TASKBAR_EXPAND";
+            case CUJ_TASKBAR_COLLAPSE:
+                return "TASKBAR_COLLAPSE";
+            case CUJ_SHADE_CLEAR_ALL:
+                return "SHADE_CLEAR_ALL";
+            case CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION:
+                return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION";
+            case CUJ_LOCKSCREEN_OCCLUSION:
+                return "LOCKSCREEN_OCCLUSION";
+            case CUJ_RECENTS_SCROLLING:
+                return "RECENTS_SCROLLING";
+            case CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS:
+                return "LAUNCHER_APP_SWIPE_TO_RECENTS";
+            case CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE:
+                return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
+            case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
+                return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
+            case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION:
+                return "LOCKSCREEN_CLOCK_MOVE_ANIMATION";
+            case CUJ_LAUNCHER_OPEN_SEARCH_RESULT:
+                return "LAUNCHER_OPEN_SEARCH_RESULT";
+            case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK:
+                return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK";
+            case CUJ_IME_INSETS_SHOW_ANIMATION:
+                return "IME_INSETS_SHOW_ANIMATION";
+            case CUJ_IME_INSETS_HIDE_ANIMATION:
+                return "IME_INSETS_HIDE_ANIMATION";
+            case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER:
+                return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER";
+            case CUJ_LAUNCHER_UNFOLD_ANIM:
+                return "LAUNCHER_UNFOLD_ANIM";
+            case CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY:
+                return "PREDICTIVE_BACK_CROSS_ACTIVITY";
+            case CUJ_PREDICTIVE_BACK_CROSS_TASK:
+                return "PREDICTIVE_BACK_CROSS_TASK";
+            case CUJ_PREDICTIVE_BACK_HOME:
+                return "PREDICTIVE_BACK_HOME";
+        }
+        return "UNKNOWN";
+    }
+
+    public static int getStatsdInteractionType(@CujType int cujType) {
+        return CUJ_TO_STATSD_INTERACTION_TYPE[cujType];
+    }
+
+    /** Returns whether the measurements for the given CUJ should be written to statsd. */
+    public static boolean logToStatsd(@CujType int cujType) {
+        return getStatsdInteractionType(cujType) != NO_STATSD_LOGGING;
+    }
+
+    /**
+     * A helper method to translate interaction type to CUJ name.
+     *
+     * @param interactionType the interaction type defined in AtomsProto.java
+     * @return the name of the interaction type
+     */
+    public static String getNameOfInteraction(int interactionType) {
+        // There is an offset amount of 1 between cujType and interactionType.
+        return Cuj.getNameOfCuj(getCujTypeFromInteraction(interactionType));
+    }
+
+    /**
+     * A helper method to translate interaction type to CUJ type.
+     *
+     * @param interactionType the interaction type defined in AtomsProto.java
+     * @return the integer in {@link Cuj.CujType}
+     */
+    private static int getCujTypeFromInteraction(int interactionType) {
+        return interactionType - 1;
+    }
+}
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index c83452d..86729f7 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -53,7 +53,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.DisplayRefreshRate.RefreshRate;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
-import com.android.internal.jank.InteractionJankMonitor.Session;
 import com.android.internal.util.FrameworkStatsLog;
 
 import java.lang.annotation.Retention;
@@ -67,7 +66,6 @@
 public class FrameTracker extends SurfaceControl.OnJankDataListener
         implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
     private static final String TAG = "FrameTracker";
-    private static final boolean DEBUG = false;
 
     private static final long INVALID_ID = -1;
     public static final int NANOS_IN_MILLISECOND = 1_000_000;
@@ -93,20 +91,19 @@
             REASON_CANCEL_NORMAL,
             REASON_CANCEL_NOT_BEGUN,
             REASON_CANCEL_SAME_VSYNC,
+            REASON_CANCEL_TIMEOUT,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Reasons {
     }
 
-    @VisibleForTesting
-    public final InteractionJankMonitor mMonitor;
     private final HardwareRendererObserver mObserver;
     private final int mTraceThresholdMissedFrames;
     private final int mTraceThresholdFrameTimeMillis;
     private final ThreadedRendererWrapper mRendererWrapper;
     private final FrameMetricsWrapper mMetricsWrapper;
     private final SparseArray<JankInfo> mJankInfos = new SparseArray<>();
-    private final Session mSession;
+    private final Configuration mConfig;
     private final ViewRootWrapper mViewRoot;
     private final SurfaceControlWrapper mSurfaceControlWrapper;
     private final int mDisplayId;
@@ -197,19 +194,18 @@
         }
     }
 
-    public FrameTracker(@NonNull InteractionJankMonitor monitor, @NonNull Session session,
-            @NonNull Handler handler, @Nullable ThreadedRendererWrapper renderer,
+    public FrameTracker(@NonNull Configuration config,
+            @Nullable ThreadedRendererWrapper renderer,
             @Nullable ViewRootWrapper viewRootWrapper,
             @NonNull SurfaceControlWrapper surfaceControlWrapper,
             @NonNull ChoreographerWrapper choreographer,
             @Nullable FrameMetricsWrapper metrics,
             @NonNull StatsLogWrapper statsLog,
             int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis,
-            @Nullable FrameTrackerListener listener, @NonNull Configuration config) {
-        mMonitor = monitor;
+            @Nullable FrameTrackerListener listener) {
         mSurfaceOnly = config.isSurfaceOnly();
-        mSession = session;
-        mHandler = handler;
+        mConfig = config;
+        mHandler = config.getHandler();
         mChoreographer = choreographer;
         mSurfaceControlWrapper = surfaceControlWrapper;
         mStatsLog = statsLog;
@@ -222,7 +218,7 @@
         mObserver = mSurfaceOnly
                 ? null
                 : new HardwareRendererObserver(this, mMetricsWrapper.getTiming(),
-                        handler, /* waitForPresentTime= */ false);
+                        mHandler, /* waitForPresentTime= */ false);
 
         mTraceThresholdMissedFrames = traceThresholdMissedFrames;
         mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis;
@@ -242,7 +238,7 @@
             mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() {
                 @Override
                 public void surfaceCreated(SurfaceControl.Transaction t) {
-                    getHandler().runWithScissors(() -> {
+                    mHandler.runWithScissors(() -> {
                         if (mSurfaceControl == null) {
                             mSurfaceControl = mViewRoot.getSurfaceControl();
                             if (mBeginVsyncId != INVALID_ID) {
@@ -262,13 +258,7 @@
 
                     // Wait a while to give the system a chance for the remaining
                     // frames to arrive, then force finish the session.
-                    getHandler().postDelayed(() -> {
-                        if (DEBUG) {
-                            Log.d(TAG, "surfaceDestroyed: " + mSession.getName()
-                                    + ", finalized=" + mMetricsFinalized
-                                    + ", info=" + mJankInfos.size()
-                                    + ", vsync=" + mBeginVsyncId);
-                        }
+                    mHandler.postDelayed(() -> {
                         if (!mMetricsFinalized) {
                             end(REASON_END_SURFACE_DESTROYED);
                             finish();
@@ -282,11 +272,6 @@
         }
     }
 
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public Handler getHandler() {
-        return mHandler;
-    }
-
     /**
      * Begin a trace session of the CUJ.
      */
@@ -300,10 +285,6 @@
             mBeginVsyncId = mDeferMonitoring ? currentVsync + 1 : currentVsync;
         }
         if (mSurfaceControl != null) {
-            if (DEBUG) {
-                Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId
-                        + ", defer=" + mDeferMonitoring + ", current=" + currentVsync);
-            }
             if (mDeferMonitoring && currentVsync < mBeginVsyncId) {
                 markEvent("FT#deferMonitoring", 0);
                 // Normal case, we begin the instrument from the very beginning,
@@ -314,11 +295,6 @@
                 // there is no need to skip the frame where the begin invocation happens.
                 beginInternal();
             }
-        } else {
-            if (DEBUG) {
-                Log.d(TAG, "begin: defer beginning since the surface is not ready for CUJ="
-                        + mSession.getName());
-            }
         }
     }
 
@@ -336,8 +312,8 @@
             return;
         }
         mTracingStarted = true;
-        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, mSession.getName(), mSession.getName(),
-                (int) mBeginVsyncId);
+        String name = mConfig.getSessionName();
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, name, name, (int) mBeginVsyncId);
         markEvent("FT#beginVsync", mBeginVsyncId);
         markEvent("FT#layerId", mSurfaceControl.getLayerId());
         mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
@@ -361,15 +337,10 @@
         } else if (mEndVsyncId <= mBeginVsyncId) {
             return cancel(REASON_CANCEL_SAME_VSYNC);
         } else {
-            if (DEBUG) {
-                Log.d(TAG, "end: " + mSession.getName()
-                        + ", end=" + mEndVsyncId + ", reason=" + reason);
-            }
+            final String name = mConfig.getSessionName();
             markEvent("FT#end", reason);
             markEvent("FT#endVsync", mEndVsyncId);
-            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, mSession.getName(),
-                    (int) mBeginVsyncId);
-            mSession.setReason(reason);
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, name, (int) mBeginVsyncId);
 
             // We don't remove observer here,
             // will remove it when all the frame metrics in this duration are called back.
@@ -395,16 +366,16 @@
                         mFlushAttempts++;
                     } else {
                         mWaitForFinishTimedOut = () -> {
-                            Log.e(TAG, "force finish cuj, time out: " + mSession.getName());
+                            Log.e(TAG, "force finish cuj, time out: " + name);
                             finish();
                         };
                         delay = TimeUnit.SECONDS.toMillis(10);
                     }
-                    getHandler().postDelayed(mWaitForFinishTimedOut, delay);
+                    mHandler.postDelayed(mWaitForFinishTimedOut, delay);
                 }
             };
-            getHandler().postDelayed(mWaitForFinishTimedOut, FLUSH_DELAY_MILLISECOND);
-            notifyCujEvent(ACTION_SESSION_END);
+            mHandler.postDelayed(mWaitForFinishTimedOut, FLUSH_DELAY_MILLISECOND);
+            notifyCujEvent(ACTION_SESSION_END, reason);
             return true;
         }
     }
@@ -421,22 +392,16 @@
         markEvent("FT#cancel", reason);
         // We don't need to end the trace section if it has never begun.
         if (mTracingStarted) {
-            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, mSession.getName(),
-                    (int) mBeginVsyncId);
+            Trace.asyncTraceForTrackEnd(
+                    Trace.TRACE_TAG_APP, mConfig.getSessionName(), (int) mBeginVsyncId);
         }
 
         // Always remove the observers in cancel call to avoid leakage.
         removeObservers();
 
-        if (DEBUG) {
-            Log.d(TAG, "cancel: " + mSession.getName() + ", begin=" + mBeginVsyncId
-                    + ", end=" + mEndVsyncId + ", reason=" + reason);
-        }
-
-        mSession.setReason(reason);
         // Notify the listener the session has been cancelled.
         // We don't notify the listeners if the session never begun.
-        notifyCujEvent(ACTION_SESSION_CANCEL);
+        notifyCujEvent(ACTION_SESSION_CANCEL, reason);
         return true;
     }
 
@@ -455,13 +420,13 @@
                         "The length of the trace event description <%s> exceeds %d",
                         event, MAX_LENGTH_EVENT_DESC));
             }
-            Trace.instantForTrack(Trace.TRACE_TAG_APP, mSession.getName(), event);
+            Trace.instantForTrack(Trace.TRACE_TAG_APP, mConfig.getSessionName(), event);
         }
     }
 
-    private void notifyCujEvent(String action) {
+    private void notifyCujEvent(String action, @Reasons int reason) {
         if (mListener == null) return;
-        mListener.onCujEvents(mSession, action);
+        mListener.onCujEvents(this, action, reason);
     }
 
     @Override
@@ -496,7 +461,7 @@
      */
     @VisibleForTesting
     public void postCallback(Runnable callback) {
-        getHandler().post(callback);
+        mHandler.post(callback);
     }
 
     @Nullable
@@ -587,13 +552,15 @@
         if (mMetricsFinalized || mCancelled) return;
         mMetricsFinalized = true;
 
-        getHandler().removeCallbacks(mWaitForFinishTimedOut);
+        mHandler.removeCallbacks(mWaitForFinishTimedOut);
         mWaitForFinishTimedOut = null;
         markEvent("FT#finish", mJankInfos.size());
 
         // The tracing has been ended, remove the observer, see if need to trigger perfetto.
         removeObservers();
 
+        final String name = mConfig.getSessionName();
+
         int totalFramesCount = 0;
         long maxFrameTimeNanos = 0;
         int missedFramesCount = 0;
@@ -616,7 +583,7 @@
                 totalFramesCount++;
                 boolean missedFrame = false;
                 if ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0) {
-                    Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + mSession.getName());
+                    Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + name);
                     missedAppFramesCount++;
                     missedFrame = true;
                 }
@@ -625,7 +592,7 @@
                         || (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0
                         || (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0
                         || (info.jankType & PREDICTION_ERROR) != 0) {
-                    Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + mSession.getName());
+                    Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + name);
                     missedSfFramesCount++;
                     missedFrame = true;
                 }
@@ -646,7 +613,7 @@
                 if (!mSurfaceOnly && !info.hwuiCallbackFired) {
                     markEvent("FT#MissedHWUICallback", info.frameVsyncId);
                     Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId
-                            + ", CUJ=" + mSession.getName());
+                            + ", CUJ=" + name);
                 }
             }
             if (!mSurfaceOnly && info.hwuiCallbackFired) {
@@ -654,7 +621,7 @@
                 if (!info.surfaceControlCallbackFired) {
                     markEvent("FT#MissedSFCallback", info.frameVsyncId);
                     Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId
-                            + ", CUJ=" + mSession.getName());
+                            + ", CUJ=" + name);
                 }
             }
         }
@@ -662,29 +629,26 @@
                 maxSuccessiveMissedFramesCount, successiveMissedFramesCount);
 
         // Log the frame stats as counters to make them easily accessible in traces.
-        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedFrames",
-                missedFramesCount);
-        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedAppFrames",
-                missedAppFramesCount);
-        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedSfFrames",
-                missedSfFramesCount);
-        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#totalFrames",
-                totalFramesCount);
-        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxFrameTimeMillis",
+        Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedFrames", missedFramesCount);
+        Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedAppFrames", missedAppFramesCount);
+        Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedSfFrames", missedSfFramesCount);
+        Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#totalFrames", totalFramesCount);
+        Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#maxFrameTimeMillis",
                 (int) (maxFrameTimeNanos / NANOS_IN_MILLISECOND));
-        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxSuccessiveMissedFrames",
+        Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#maxSuccessiveMissedFrames",
                 maxSuccessiveMissedFramesCount);
 
         // Trigger perfetto if necessary.
-        if (shouldTriggerPerfetto(missedFramesCount, (int) maxFrameTimeNanos)) {
-            triggerPerfetto();
+        if (mListener != null
+                && shouldTriggerPerfetto(missedFramesCount, (int) maxFrameTimeNanos)) {
+            mListener.triggerPerfetto(mConfig);
         }
-        if (mSession.logToStatsd()) {
+        if (mConfig.logToStatsd()) {
             mStatsLog.write(
                     FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED,
                     mDisplayId,
                     refreshRate,
-                    mSession.getStatsdInteractionType(),
+                    mConfig.getStatsdInteractionType(),
                     totalFramesCount,
                     missedFramesCount,
                     maxFrameTimeNanos, /* will be 0 if mSurfaceOnly == true */
@@ -692,16 +656,6 @@
                     missedAppFramesCount,
                     maxSuccessiveMissedFramesCount);
         }
-        if (DEBUG) {
-            Log.i(TAG, "finish: CUJ=" + mSession.getName()
-                    + " (" + mBeginVsyncId + "," + mEndVsyncId + ")"
-                    + " totalFrames=" + totalFramesCount
-                    + " missedAppFrames=" + missedAppFramesCount
-                    + " missedSfFrames=" + missedSfFramesCount
-                    + " missedFrames=" + missedFramesCount
-                    + " maxFrameTimeMillis=" + maxFrameTimeNanos / NANOS_IN_MILLISECOND
-                    + " maxSuccessiveMissedFramesCount=" + maxSuccessiveMissedFramesCount);
-        }
     }
 
     ThreadedRendererWrapper getThreadedRenderer() {
@@ -737,13 +691,6 @@
     }
 
     /**
-     * Trigger the prefetto daemon.
-     */
-    public void triggerPerfetto() {
-        mMonitor.trigger(mSession);
-    }
-
-    /**
      * A wrapper class that we can spy FrameMetrics (a final class) in unit tests.
      */
     public static class FrameMetricsWrapper {
@@ -895,9 +842,17 @@
         /**
          * Notify that the CUJ session was created.
          *
-         * @param session the CUJ session
+         * @param tracker the tracker
          * @param action the specific action
+         * @param reason the reason for the action
          */
-        void onCujEvents(Session session, String action);
+        void onCujEvents(FrameTracker tracker, String action, @Reasons int reason);
+
+        /**
+         * Notify that the Perfetto trace should be triggered.
+         *
+         * @param config the tracker configuration
+         */
+        void triggerPerfetto(Configuration config);
     }
 }
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index e6b036c..8b1879f 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -23,89 +23,9 @@
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
-import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
 
 import android.Manifest;
 import android.annotation.ColorInt;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.UiThread;
@@ -137,34 +57,31 @@
 import com.android.internal.jank.FrameTracker.ViewRootWrapper;
 import com.android.internal.util.PerfettoTrigger;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.time.Instant;
-import java.util.Locale;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
 
 /**
- * This class let users to begin and end the always on tracing mechanism.
+ * This class lets users begin and end the always on tracing mechanism.
  *
  * Enabling for local development:
- *
+ *<pre>
  * adb shell device_config put interaction_jank_monitor enabled true
  * adb shell device_config put interaction_jank_monitor sampling_interval 1
- *
+ * </pre>
  * On debuggable builds, an overlay can be used to display the name of the
  * currently running cuj using:
- *
+ * <pre>
  * adb shell device_config put interaction_jank_monitor debug_overlay_enabled true
- *
- * NOTE: The overlay will interfere with metrics, so it should only be used
- * for understanding which UI events correspeond to which CUJs.
+ * </pre>
+ * <b>NOTE</b>: The overlay will interfere with metrics, so it should only be used
+ * for understanding which UI events correspond to which CUJs.
  *
  * @hide
  */
 public class InteractionJankMonitor {
     private static final String TAG = InteractionJankMonitor.class.getSimpleName();
-    private static final boolean DEBUG = false;
     private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName();
 
     private static final String DEFAULT_WORKER_NAME = TAG + "-Worker";
@@ -186,218 +103,79 @@
     private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64;
     private static final boolean DEFAULT_DEBUG_OVERLAY_ENABLED = false;
 
-    @VisibleForTesting
-    public static final int MAX_LENGTH_OF_CUJ_NAME = 80;
     private static final int MAX_LENGTH_SESSION_NAME = 100;
 
     public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END";
     public static final String ACTION_SESSION_CANCEL = ACTION_PREFIX + ".ACTION_SESSION_CANCEL";
 
-    // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
-    public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
-    public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2;
-    public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3;
-    public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4;
-    public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = 5;
-    public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = 6;
-    public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS = 7;
-    public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON = 8;
-    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 9;
-    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 10;
-    public static final int CUJ_LAUNCHER_QUICK_SWITCH = 11;
-    public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = 12;
-    public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = 13;
-    public static final int CUJ_NOTIFICATION_ADD = 14;
-    public static final int CUJ_NOTIFICATION_REMOVE = 15;
-    public static final int CUJ_NOTIFICATION_APP_START = 16;
-    public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = 17;
-    public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = 18;
-    public static final int CUJ_LOCKSCREEN_PIN_APPEAR = 19;
-    public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = 20;
-    public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = 21;
-    public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = 22;
-    public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = 23;
-    public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = 24;
-    public static final int CUJ_LAUNCHER_OPEN_ALL_APPS = 25;
-    public static final int CUJ_LAUNCHER_ALL_APPS_SCROLL = 26;
-    public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET = 27;
-    public static final int CUJ_SETTINGS_PAGE_SCROLL = 28;
-    public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = 29;
-    public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = 30;
-    public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = 31;
-    public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32;
-    public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33;
-    public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34;
-    public static final int CUJ_PIP_TRANSITION = 35;
-    public static final int CUJ_WALLPAPER_TRANSITION = 36;
-    public static final int CUJ_USER_SWITCH = 37;
-    public static final int CUJ_SPLASHSCREEN_AVD = 38;
-    public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39;
-    public static final int CUJ_SCREEN_OFF = 40;
-    public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41;
-    public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42;
-    public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43;
-    public static final int CUJ_UNFOLD_ANIM = 44;
-    public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = 45;
-    public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = 46;
-    public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = 47;
-    public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = 48;
-    public static final int CUJ_SPLIT_SCREEN_ENTER = 49;
-    public static final int CUJ_SPLIT_SCREEN_EXIT = 50;
-    public static final int CUJ_LOCKSCREEN_LAUNCH_CAMERA = 51; // reserved.
-    public static final int CUJ_SPLIT_SCREEN_RESIZE = 52;
-    public static final int CUJ_SETTINGS_SLIDER = 53;
-    public static final int CUJ_TAKE_SCREENSHOT = 54;
-    public static final int CUJ_VOLUME_CONTROL = 55;
-    public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = 56;
-    public static final int CUJ_SETTINGS_TOGGLE = 57;
-    public static final int CUJ_SHADE_DIALOG_OPEN = 58;
-    public static final int CUJ_USER_DIALOG_OPEN = 59;
-    public static final int CUJ_TASKBAR_EXPAND = 60;
-    public static final int CUJ_TASKBAR_COLLAPSE = 61;
-    public static final int CUJ_SHADE_CLEAR_ALL = 62;
-    public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63;
-    public static final int CUJ_LOCKSCREEN_OCCLUSION = 64;
-    public static final int CUJ_RECENTS_SCROLLING = 65;
-    public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
-    public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
-    public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
-    public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
-    public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71;
-    // 72 - 77 are reserved for b/281564325.
-
-    /**
-     * In some cases when we do not have any end-target, we play a simple slide-down animation.
-     * eg: Open an app from Overview/Task switcher such that there is no home-screen icon.
-     * eg: Exit the app using back gesture.
-     */
-    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78;
-    public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80;
-    public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81;
-
-    public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82;
-
-    public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83;
-
-    public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84;
-    public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
-    public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
-
-    private static final int LAST_CUJ = CUJ_PREDICTIVE_BACK_HOME;
-    private static final int NO_STATSD_LOGGING = -1;
-
-    // Used to convert CujType to InteractionType enum value for statsd logging.
-    // Use NO_STATSD_LOGGING in case the measurement for a given CUJ should not be logged to statsd.
-    @VisibleForTesting
-    public static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = new int[LAST_CUJ + 1];
-
-    static {
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[1] = NO_STATSD_LOGGING; // This is deprecated.
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_SCROLL_FLING] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_EXPAND] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_PIP] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_QUICK_SWITCH] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_ADD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_REMOVE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_APP_START] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_FROM_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_TO_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_ALL_APPS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_ALL_APPS_SCROLL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_PAGE_SCROLL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_UNLOCK_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PIP_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_SWITCH] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_AVD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_EXIT_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF_SHOW_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_ENTER_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_EXIT_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_NEXT_FLOW] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_SCREEN_FOR_STATUS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_ENTER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_EXIT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_LAUNCH_CAMERA] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_RESIZE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_SLIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TAKE_SCREENSHOT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_VOLUME_CONTROL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BIOMETRIC_PROMPT_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_TOGGLE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_DIALOG_OPEN] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_DIALOG_OPEN] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_EXPAND] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_CLEAR_ALL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_OCCLUSION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_RECENTS_SCROLLING] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
-        CUJ_TO_STATSD_INTERACTION_TYPE[69] = NO_STATSD_LOGGING; // This is deprecated.
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
-        // 72 - 77 are reserved for b/281564325.
-        CUJ_TO_STATSD_INTERACTION_TYPE[72] = NO_STATSD_LOGGING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[73] = NO_STATSD_LOGGING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[74] = NO_STATSD_LOGGING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[75] = NO_STATSD_LOGGING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[76] = NO_STATSD_LOGGING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[77] = NO_STATSD_LOGGING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
-        CUJ_TO_STATSD_INTERACTION_TYPE[79] = NO_STATSD_LOGGING; // This is deprecated.
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] =
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] =
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] =
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
-    }
+    // These are not the CUJ constants you are looking for. These constants simply forward their
+    // definition from {@link Cuj}. They are here only as a transition measure until all references
+    // have been updated to the new location.
+    @Deprecated public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
+    @Deprecated public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
+    @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND;
+    @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
+    @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
+    @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE;
+    @Deprecated public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
+    @Deprecated public static final int CUJ_LAUNCHER_QUICK_SWITCH = Cuj.CUJ_LAUNCHER_QUICK_SWITCH;
+    @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR;
+    @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR;
+    @Deprecated public static final int CUJ_NOTIFICATION_ADD = Cuj.CUJ_NOTIFICATION_ADD;
+    @Deprecated public static final int CUJ_NOTIFICATION_REMOVE = Cuj.CUJ_NOTIFICATION_REMOVE;
+    @Deprecated public static final int CUJ_NOTIFICATION_APP_START = Cuj.CUJ_NOTIFICATION_APP_START;
+    @Deprecated public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = Cuj.CUJ_LOCKSCREEN_PASSWORD_APPEAR;
+    @Deprecated public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = Cuj.CUJ_LOCKSCREEN_PATTERN_APPEAR;
+    @Deprecated public static final int CUJ_LOCKSCREEN_PIN_APPEAR = Cuj.CUJ_LOCKSCREEN_PIN_APPEAR;
+    @Deprecated public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR;
+    @Deprecated public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR;
+    @Deprecated public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PIN_DISAPPEAR;
+    @Deprecated public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = Cuj.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
+    @Deprecated public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = Cuj.CUJ_LOCKSCREEN_TRANSITION_TO_AOD;
+    @Deprecated public static final int CUJ_SETTINGS_PAGE_SCROLL = Cuj.CUJ_SETTINGS_PAGE_SCROLL;
+    @Deprecated public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = Cuj.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
+    @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
+    @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
+    @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE;
+    @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
+    @Deprecated public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
+    @Deprecated public static final int CUJ_PIP_TRANSITION = Cuj.CUJ_PIP_TRANSITION;
+    @Deprecated public static final int CUJ_USER_SWITCH = Cuj.CUJ_USER_SWITCH;
+    @Deprecated public static final int CUJ_SPLASHSCREEN_AVD = Cuj.CUJ_SPLASHSCREEN_AVD;
+    @Deprecated public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = Cuj.CUJ_SPLASHSCREEN_EXIT_ANIM;
+    @Deprecated public static final int CUJ_SCREEN_OFF = Cuj.CUJ_SCREEN_OFF;
+    @Deprecated public static final int CUJ_SCREEN_OFF_SHOW_AOD = Cuj.CUJ_SCREEN_OFF_SHOW_AOD;
+    @Deprecated public static final int CUJ_UNFOLD_ANIM = Cuj.CUJ_UNFOLD_ANIM;
+    @Deprecated public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = Cuj.CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
+    @Deprecated public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = Cuj.CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
+    @Deprecated public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = Cuj.CUJ_SUW_LOADING_TO_NEXT_FLOW;
+    @Deprecated public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = Cuj.CUJ_SUW_LOADING_SCREEN_FOR_STATUS;
+    @Deprecated public static final int CUJ_SPLIT_SCREEN_RESIZE = Cuj.CUJ_SPLIT_SCREEN_RESIZE;
+    @Deprecated public static final int CUJ_SETTINGS_SLIDER = Cuj.CUJ_SETTINGS_SLIDER;
+    @Deprecated public static final int CUJ_TAKE_SCREENSHOT = Cuj.CUJ_TAKE_SCREENSHOT;
+    @Deprecated public static final int CUJ_VOLUME_CONTROL = Cuj.CUJ_VOLUME_CONTROL;
+    @Deprecated public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = Cuj.CUJ_BIOMETRIC_PROMPT_TRANSITION;
+    @Deprecated public static final int CUJ_SETTINGS_TOGGLE = Cuj.CUJ_SETTINGS_TOGGLE;
+    @Deprecated public static final int CUJ_SHADE_DIALOG_OPEN = Cuj.CUJ_SHADE_DIALOG_OPEN;
+    @Deprecated public static final int CUJ_USER_DIALOG_OPEN = Cuj.CUJ_USER_DIALOG_OPEN;
+    @Deprecated public static final int CUJ_TASKBAR_EXPAND = Cuj.CUJ_TASKBAR_EXPAND;
+    @Deprecated public static final int CUJ_TASKBAR_COLLAPSE = Cuj.CUJ_TASKBAR_COLLAPSE;
+    @Deprecated public static final int CUJ_SHADE_CLEAR_ALL = Cuj.CUJ_SHADE_CLEAR_ALL;
+    @Deprecated public static final int CUJ_LOCKSCREEN_OCCLUSION = Cuj.CUJ_LOCKSCREEN_OCCLUSION;
+    @Deprecated public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = Cuj.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
+    @Deprecated public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = Cuj.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+    @Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
+    @Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = Cuj.CUJ_PREDICTIVE_BACK_CROSS_TASK;
+    @Deprecated public static final int CUJ_PREDICTIVE_BACK_HOME = Cuj.CUJ_PREDICTIVE_BACK_HOME;
 
     private static class InstanceHolder {
         public static final InteractionJankMonitor INSTANCE =
             new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME));
     }
 
-    private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
-            this::updateProperties;
-
     @GuardedBy("mLock")
-    private final SparseArray<FrameTracker> mRunningTrackers;
-    @GuardedBy("mLock")
-    private final SparseArray<Runnable> mTimeoutActions;
-    private final HandlerThread mWorker;
+    private final SparseArray<RunningTracker> mRunningTrackers = new SparseArray<>();
+    private final Handler mWorker;
     private final DisplayResolutionTracker mDisplayResolutionTracker;
     private final Object mLock = new Object();
     private @ColorInt int mDebugBgColor = Color.CYAN;
@@ -409,91 +187,6 @@
     private int mTraceThresholdMissedFrames = DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES;
     private int mTraceThresholdFrameTimeMillis = DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS;
 
-    /** @hide */
-    @IntDef({
-            CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
-            CUJ_NOTIFICATION_SHADE_SCROLL_FLING,
-            CUJ_NOTIFICATION_SHADE_ROW_EXPAND,
-            CUJ_NOTIFICATION_SHADE_ROW_SWIPE,
-            CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
-            CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE,
-            CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS,
-            CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON,
-            CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
-            CUJ_LAUNCHER_APP_CLOSE_TO_PIP,
-            CUJ_LAUNCHER_QUICK_SWITCH,
-            CUJ_NOTIFICATION_HEADS_UP_APPEAR,
-            CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR,
-            CUJ_NOTIFICATION_ADD,
-            CUJ_NOTIFICATION_REMOVE,
-            CUJ_NOTIFICATION_APP_START,
-            CUJ_LOCKSCREEN_PASSWORD_APPEAR,
-            CUJ_LOCKSCREEN_PATTERN_APPEAR,
-            CUJ_LOCKSCREEN_PIN_APPEAR,
-            CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR,
-            CUJ_LOCKSCREEN_PATTERN_DISAPPEAR,
-            CUJ_LOCKSCREEN_PIN_DISAPPEAR,
-            CUJ_LOCKSCREEN_TRANSITION_FROM_AOD,
-            CUJ_LOCKSCREEN_TRANSITION_TO_AOD,
-            CUJ_LAUNCHER_OPEN_ALL_APPS,
-            CUJ_LAUNCHER_ALL_APPS_SCROLL,
-            CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET,
-            CUJ_SETTINGS_PAGE_SCROLL,
-            CUJ_LOCKSCREEN_UNLOCK_ANIMATION,
-            CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON,
-            CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER,
-            CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
-            CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
-            CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
-            CUJ_PIP_TRANSITION,
-            CUJ_WALLPAPER_TRANSITION,
-            CUJ_USER_SWITCH,
-            CUJ_SPLASHSCREEN_AVD,
-            CUJ_SPLASHSCREEN_EXIT_ANIM,
-            CUJ_SCREEN_OFF,
-            CUJ_SCREEN_OFF_SHOW_AOD,
-            CUJ_ONE_HANDED_ENTER_TRANSITION,
-            CUJ_ONE_HANDED_EXIT_TRANSITION,
-            CUJ_UNFOLD_ANIM,
-            CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS,
-            CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS,
-            CUJ_SUW_LOADING_TO_NEXT_FLOW,
-            CUJ_SUW_LOADING_SCREEN_FOR_STATUS,
-            CUJ_SPLIT_SCREEN_ENTER,
-            CUJ_SPLIT_SCREEN_EXIT,
-            CUJ_LOCKSCREEN_LAUNCH_CAMERA,
-            CUJ_SPLIT_SCREEN_RESIZE,
-            CUJ_SETTINGS_SLIDER,
-            CUJ_TAKE_SCREENSHOT,
-            CUJ_VOLUME_CONTROL,
-            CUJ_BIOMETRIC_PROMPT_TRANSITION,
-            CUJ_SETTINGS_TOGGLE,
-            CUJ_SHADE_DIALOG_OPEN,
-            CUJ_USER_DIALOG_OPEN,
-            CUJ_TASKBAR_EXPAND,
-            CUJ_TASKBAR_COLLAPSE,
-            CUJ_SHADE_CLEAR_ALL,
-            CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
-            CUJ_LOCKSCREEN_OCCLUSION,
-            CUJ_RECENTS_SCROLLING,
-            CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
-            CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
-            CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
-            CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION,
-            CUJ_LAUNCHER_OPEN_SEARCH_RESULT,
-            CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK,
-            CUJ_IME_INSETS_SHOW_ANIMATION,
-            CUJ_IME_INSETS_HIDE_ANIMATION,
-            CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
-            CUJ_LAUNCHER_UNFOLD_ANIM,
-            CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
-            CUJ_PREDICTIVE_BACK_CROSS_TASK,
-            CUJ_PREDICTIVE_BACK_HOME,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface CujType {
-    }
-
     /**
      * Get the singleton of InteractionJankMonitor.
      *
@@ -511,71 +204,44 @@
     @VisibleForTesting
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     public InteractionJankMonitor(@NonNull HandlerThread worker) {
-        mRunningTrackers = new SparseArray<>();
-        mTimeoutActions = new SparseArray<>();
-        mWorker = worker;
-        mWorker.start();
-        mDisplayResolutionTracker = new DisplayResolutionTracker(worker.getThreadHandler());
-        mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
-        mEnabled = DEFAULT_ENABLED;
+        worker.start();
+        mWorker = worker.getThreadHandler();
+        mDisplayResolutionTracker = new DisplayResolutionTracker(mWorker);
 
         final Context context = ActivityThread.currentApplication();
-        if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
-            if (DEBUG) {
-                Log.d(TAG, "Initialized the InteractionJankMonitor."
-                        + " (No READ_DEVICE_CONFIG permission to change configs)"
-                        + " enabled=" + mEnabled + ", interval=" + mSamplingInterval
-                        + ", missedFrameThreshold=" + mTraceThresholdMissedFrames
-                        + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
-                        + ", package=" + context.getPackageName());
-            }
+        if (context == null || context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
+            Log.w(TAG, "Initializing without READ_DEVICE_CONFIG permission."
+                    + " enabled=" + mEnabled + ", interval=" + mSamplingInterval
+                    + ", missedFrameThreshold=" + mTraceThresholdMissedFrames
+                    + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
+                    + ", package=" + (context == null ? "null" : context.getPackageName()));
             return;
         }
 
         // Post initialization to the background in case we're running on the main thread.
-        mWorker.getThreadHandler().post(
-                () -> {
-                    try {
-                        mPropertiesChangedListener.onPropertiesChanged(
-                                DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR));
-                        DeviceConfig.addOnPropertiesChangedListener(
-                                NAMESPACE_INTERACTION_JANK_MONITOR,
-                                new HandlerExecutor(mWorker.getThreadHandler()),
-                                mPropertiesChangedListener);
-                    } catch (SecurityException ex) {
-                        Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
-                                + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
-                                + ", package=" + context.getPackageName());
-                    }
-                });
+        mWorker.post(() -> {
+            try {
+                updateProperties(DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR));
+                DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_INTERACTION_JANK_MONITOR,
+                        new HandlerExecutor(mWorker), this::updateProperties);
+            } catch (SecurityException ex) {
+                Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
+                        + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+                        + ", package=" + context.getPackageName());
+            }
+        });
     }
 
     /**
      * Creates a {@link FrameTracker} instance.
      *
-     * @param config the config used in instrumenting
-     * @param session the session associates with this tracker
+     * @param config the conifg associates with this tracker
      * @return instance of the FrameTracker
      */
     @VisibleForTesting
-    public FrameTracker createFrameTracker(Configuration config, Session session) {
+    public FrameTracker createFrameTracker(Configuration config) {
         final View view = config.mView;
 
-        if (!config.hasValidView()) {
-            boolean attached = false;
-            boolean hasViewRoot = false;
-            boolean hasRenderer = false;
-            if (view != null) {
-                attached = view.isAttachedToWindow();
-                hasViewRoot = view.getViewRootImpl() != null;
-                hasRenderer = view.getThreadedRenderer() != null;
-            }
-            Log.d(TAG, "create FrameTracker fails: view=" + view
-                    + ", attached=" + attached + ", hasViewRoot=" + hasViewRoot
-                    + ", hasRenderer=" + hasRenderer, new Throwable());
-            return null;
-        }
-
         final ThreadedRendererWrapper threadedRenderer =
                 view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer());
         final ViewRootWrapper viewRoot =
@@ -583,52 +249,50 @@
         final SurfaceControlWrapper surfaceControl = new SurfaceControlWrapper();
         final ChoreographerWrapper choreographer =
                 new ChoreographerWrapper(Choreographer.getInstance());
-        final FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(act, s);
+        final FrameTrackerListener eventsListener = new FrameTrackerListener() {
+            @Override
+            public void onCujEvents(FrameTracker tracker, String action, int reason) {
+                config.getHandler().runWithScissors(() ->
+                        handleCujEvents(config.mCujType, tracker, action, reason),
+                        EXECUTOR_TASK_TIMEOUT);
+            }
+
+            @Override
+            public void triggerPerfetto(Configuration config) {
+                mWorker.post(() -> PerfettoTrigger.trigger(config.getPerfettoTrigger()));
+            }
+        };
         final FrameMetricsWrapper frameMetrics = new FrameMetricsWrapper();
 
-        return new FrameTracker(this, session, config.getHandler(), threadedRenderer, viewRoot,
+        return new FrameTracker(config, threadedRenderer, viewRoot,
                 surfaceControl, choreographer, frameMetrics,
                 new FrameTracker.StatsLogWrapper(mDisplayResolutionTracker),
                 mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis,
-                eventsListener, config);
+                eventsListener);
     }
 
     @UiThread
-    private void handleCujEvents(String action, Session session) {
+    private void handleCujEvents(
+            @Cuj.CujType int cuj, FrameTracker tracker, String action, @Reasons int reason) {
         // Clear the running and timeout tasks if the end / cancel was fired within the tracker.
         // Or we might have memory leaks.
-        if (needRemoveTasks(action, session)) {
-            getTracker(session.getCuj()).getHandler().runWithScissors(() -> {
-                removeTimeout(session.getCuj());
-                removeTracker(session.getCuj(), session.getReason());
-            }, EXECUTOR_TASK_TIMEOUT);
+        if (needRemoveTasks(action, reason)) {
+            removeTrackerIfCurrent(cuj, tracker, reason);
         }
     }
 
-    private boolean needRemoveTasks(String action, Session session) {
-        final boolean badEnd = action.equals(ACTION_SESSION_END)
-                && session.getReason() != REASON_END_NORMAL;
+    private static boolean needRemoveTasks(String action, @Reasons int reason) {
+        final boolean badEnd = action.equals(ACTION_SESSION_END) && reason != REASON_END_NORMAL;
         final boolean badCancel = action.equals(ACTION_SESSION_CANCEL)
-                && !(session.getReason() == REASON_CANCEL_NORMAL
-                || session.getReason() == REASON_CANCEL_TIMEOUT);
+                && !(reason == REASON_CANCEL_NORMAL || reason == REASON_CANCEL_TIMEOUT);
         return badEnd || badCancel;
     }
 
-    private void removeTimeout(@CujType int cujType) {
-        synchronized (mLock) {
-            Runnable timeout = mTimeoutActions.get(cujType);
-            if (timeout != null) {
-                getTracker(cujType).getHandler().removeCallbacks(timeout);
-                mTimeoutActions.remove(cujType);
-            }
-        }
-    }
-
     /**
      * @param cujType cuj type
      * @return true if the cuj is under instrumenting, false otherwise.
      */
-    public boolean isInstrumenting(@CujType int cujType) {
+    public boolean isInstrumenting(@Cuj.CujType int cujType) {
         synchronized (mLock) {
             return mRunningTrackers.contains(cujType);
         }
@@ -638,10 +302,10 @@
      * Begins a trace session.
      *
      * @param v an attached view.
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link Cuj.CujType}.
      * @return boolean true if the tracker is started successfully, false otherwise.
      */
-    public boolean begin(View v, @CujType int cujType) {
+    public boolean begin(View v, @Cuj.CujType int cujType) {
         try {
             return begin(Configuration.Builder.withView(cujType, v));
         } catch (IllegalArgumentException ex) {
@@ -667,7 +331,7 @@
             final boolean success = config.getHandler().runWithScissors(
                     () -> result.mResult = beginInternal(config), EXECUTOR_TASK_TIMEOUT);
             if (!success) {
-                Log.d(TAG, "begin failed due to timeout, CUJ=" + getNameOfCuj(config.mCujType));
+                Log.d(TAG, "begin failed due to timeout, CUJ=" + Cuj.getNameOfCuj(config.mCujType));
                 return false;
             }
             return result.mResult;
@@ -680,75 +344,59 @@
     @UiThread
     private boolean beginInternal(@NonNull Configuration conf) {
         int cujType = conf.mCujType;
-        if (!shouldMonitor(cujType)) return false;
-        FrameTracker tracker = getTracker(cujType);
-        // Skip subsequent calls if we already have an ongoing tracing.
-        if (tracker != null) return false;
+        if (!shouldMonitor()) {
+            return false;
+        }
 
-        // begin a new trace session.
-        tracker = createFrameTracker(conf, new Session(cujType, conf.mTag));
-        if (tracker == null) return false;
-        putTracker(cujType, tracker);
-        tracker.begin();
+        RunningTracker tracker = putTrackerIfNoCurrent(cujType, () ->
+                new RunningTracker(
+                    conf, createFrameTracker(conf), () -> cancel(cujType, REASON_CANCEL_TIMEOUT)));
+        if (tracker == null) {
+            return false;
+        }
 
+        tracker.mTracker.begin();
         // Cancel the trace if we don't get an end() call in specified duration.
-        scheduleTimeoutAction(
-                cujType, conf.mTimeout, () -> cancel(cujType, REASON_CANCEL_TIMEOUT));
+        scheduleTimeoutAction(tracker.mConfig, tracker.mTimeoutAction);
+
         return true;
     }
 
     /**
      * Check if the monitoring is enabled and if it should be sampled.
      */
-    @SuppressWarnings("RandomModInteger")
     @VisibleForTesting
-    public boolean shouldMonitor(@CujType int cujType) {
-        boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
-        if (!mEnabled || !shouldSample) {
-            if (DEBUG) {
-                Log.d(TAG, "Skip monitoring cuj: " + getNameOfCuj(cujType)
-                        + ", enable=" + mEnabled + ", debuggable=" + DEFAULT_ENABLED
-                        + ", sample=" + shouldSample + ", interval=" + mSamplingInterval);
-            }
-            return false;
-        }
-        return true;
+    public boolean shouldMonitor() {
+        return mEnabled && (ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0);
     }
 
-    /**
-     * Schedules a timeout action.
-     * @param cuj cuj type
-     * @param timeout duration to timeout
-     * @param action action once timeout
-     */
     @VisibleForTesting
-    public void scheduleTimeoutAction(@CujType int cuj, long timeout, Runnable action) {
-        synchronized (mLock) {
-            mTimeoutActions.put(cuj, action);
-            getTracker(cuj).getHandler().postDelayed(action, timeout);
-        }
+    public void scheduleTimeoutAction(Configuration config, Runnable action) {
+        config.getHandler().postDelayed(action, config.mTimeout);
     }
 
     /**
      * Ends a trace session.
      *
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link Cuj.CujType}.
      * @return boolean true if the tracker is ended successfully, false otherwise.
      */
-    public boolean end(@CujType int cujType) {
+    public boolean end(@Cuj.CujType int cujType) {
         postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> {
             EventLogTags.writeJankCujEventsEndRequest(
                     cujType, unixNanos, elapsedNanos, realtimeNanos);
         });
-        FrameTracker tracker = getTracker(cujType);
+        RunningTracker tracker = getTracker(cujType);
         // Skip this call since we haven't started a trace yet.
-        if (tracker == null) return false;
+        if (tracker == null) {
+            return false;
+        }
         try {
             final TrackerResult result = new TrackerResult();
-            final boolean success = tracker.getHandler().runWithScissors(
-                    () -> result.mResult = endInternal(cujType), EXECUTOR_TASK_TIMEOUT);
+            final boolean success = tracker.mConfig.getHandler().runWithScissors(
+                    () -> result.mResult = endInternal(tracker), EXECUTOR_TASK_TIMEOUT);
             if (!success) {
-                Log.d(TAG, "end failed due to timeout, CUJ=" + getNameOfCuj(cujType));
+                Log.d(TAG, "end failed due to timeout, CUJ=" + Cuj.getNameOfCuj(cujType));
                 return false;
             }
             return result.mResult;
@@ -759,15 +407,11 @@
     }
 
     @UiThread
-    private boolean endInternal(@CujType int cujType) {
-        // remove the timeout action first.
-        removeTimeout(cujType);
-        FrameTracker tracker = getTracker(cujType);
-        if (tracker == null) return false;
-        // if the end call doesn't return true, another thread is handling end of the cuj.
-        if (tracker.end(REASON_END_NORMAL)) {
-            removeTracker(cujType, REASON_END_NORMAL);
+    private boolean endInternal(RunningTracker tracker) {
+        if (removeTrackerIfCurrent(tracker, REASON_END_NORMAL)) {
+            return false;
         }
+        tracker.mTracker.end(REASON_END_NORMAL);
         return true;
     }
 
@@ -776,7 +420,7 @@
      *
      * @return boolean true if the tracker is cancelled successfully, false otherwise.
      */
-    public boolean cancel(@CujType int cujType) {
+    public boolean cancel(@Cuj.CujType int cujType) {
         postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> {
             EventLogTags.writeJankCujEventsCancelRequest(
                     cujType, unixNanos, elapsedNanos, realtimeNanos);
@@ -790,16 +434,18 @@
      * @return boolean true if the tracker is cancelled successfully, false otherwise.
      */
     @VisibleForTesting
-    public boolean cancel(@CujType int cujType, @Reasons int reason) {
-        FrameTracker tracker = getTracker(cujType);
+    public boolean cancel(@Cuj.CujType int cujType, @Reasons int reason) {
+        RunningTracker tracker = getTracker(cujType);
         // Skip this call since we haven't started a trace yet.
-        if (tracker == null) return false;
+        if (tracker == null) {
+            return false;
+        }
         try {
             final TrackerResult result = new TrackerResult();
-            final boolean success = tracker.getHandler().runWithScissors(
-                    () -> result.mResult = cancelInternal(cujType, reason), EXECUTOR_TASK_TIMEOUT);
+            final boolean success = tracker.mConfig.getHandler().runWithScissors(
+                    () -> result.mResult = cancelInternal(tracker, reason), EXECUTOR_TASK_TIMEOUT);
             if (!success) {
-                Log.d(TAG, "cancel failed due to timeout, CUJ=" + getNameOfCuj(cujType));
+                Log.d(TAG, "cancel failed due to timeout, CUJ=" + Cuj.getNameOfCuj(cujType));
                 return false;
             }
             return result.mResult;
@@ -810,71 +456,86 @@
     }
 
     @UiThread
-    private boolean cancelInternal(@CujType int cujType, @Reasons int reason) {
-        // remove the timeout action first.
-        removeTimeout(cujType);
-        FrameTracker tracker = getTracker(cujType);
-        if (tracker == null) return false;
-        // if the cancel call doesn't return true, another thread is handling cancel of the cuj.
-        if (tracker.cancel(reason)) {
-            removeTracker(cujType, reason);
+    private boolean cancelInternal(RunningTracker tracker, @Reasons int reason) {
+        if (removeTrackerIfCurrent(tracker, reason)) {
+            return false;
         }
+        tracker.mTracker.cancel(reason);
         return true;
     }
 
     @UiThread
-    private void putTracker(@CujType int cuj, @NonNull FrameTracker tracker) {
+    private RunningTracker putTrackerIfNoCurrent(
+            @Cuj.CujType int cuj, Supplier<RunningTracker> supplier) {
         synchronized (mLock) {
+            if (mRunningTrackers.contains(cuj)) {
+                return null;
+            }
+
+            RunningTracker tracker = supplier.get();
+            if (tracker == null) {
+                return null;
+            }
+
             mRunningTrackers.put(cuj, tracker);
             if (mDebugOverlay != null) {
                 mDebugOverlay.onTrackerAdded(cuj, tracker);
             }
-            if (DEBUG) {
-                Log.d(TAG, "Added tracker for " + getNameOfCuj(cuj)
-                        + ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers));
-            }
+
+            return tracker;
         }
     }
 
-    private FrameTracker getTracker(@CujType int cuj) {
+    private RunningTracker getTracker(@Cuj.CujType int cuj) {
         synchronized (mLock) {
             return mRunningTrackers.get(cuj);
         }
     }
 
+    /**
+     * @return {@code true} if another tracker is current
+     */
     @UiThread
-    private void removeTracker(@CujType int cuj, int reason) {
+    private boolean removeTrackerIfCurrent(RunningTracker tracker, int reason) {
+        return removeTrackerIfCurrent(tracker.mConfig.mCujType, tracker.mTracker, reason);
+    }
+
+    /**
+     * @return {@code true} if another tracker is current
+     */
+    @UiThread
+    private boolean removeTrackerIfCurrent(@Cuj.CujType int cuj, FrameTracker tracker, int reason) {
         synchronized (mLock) {
+            RunningTracker running = mRunningTrackers.get(cuj);
+            if (running == null || running.mTracker != tracker) {
+                return true;
+            }
+
+            running.mConfig.getHandler().removeCallbacks(running.mTimeoutAction);
             mRunningTrackers.remove(cuj);
             if (mDebugOverlay != null) {
                 mDebugOverlay.onTrackerRemoved(cuj, reason, mRunningTrackers);
             }
-            if (DEBUG) {
-                Log.d(TAG, "Removed tracker for " + getNameOfCuj(cuj)
-                        + ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers));
-            }
+            return false;
         }
     }
 
     @WorkerThread
-    private void updateProperties(DeviceConfig.Properties properties) {
+    @VisibleForTesting
+    public void updateProperties(DeviceConfig.Properties properties) {
         for (String property : properties.getKeyset()) {
             switch (property) {
-                case SETTINGS_SAMPLING_INTERVAL_KEY:
-                    mSamplingInterval = properties.getInt(property, DEFAULT_SAMPLING_INTERVAL);
-                    break;
-                case SETTINGS_THRESHOLD_MISSED_FRAMES_KEY:
-                    mTraceThresholdMissedFrames =
-                            properties.getInt(property, DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES);
-                    break;
-                case SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY:
-                    mTraceThresholdFrameTimeMillis =
-                            properties.getInt(property, DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
-                    break;
-                case SETTINGS_ENABLED_KEY:
-                    mEnabled = properties.getBoolean(property, DEFAULT_ENABLED);
-                    break;
-                case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY:
+                case SETTINGS_SAMPLING_INTERVAL_KEY ->
+                        mSamplingInterval = properties.getInt(property, DEFAULT_SAMPLING_INTERVAL);
+                case SETTINGS_THRESHOLD_MISSED_FRAMES_KEY ->
+                        mTraceThresholdMissedFrames =
+                                properties.getInt(property, DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES);
+                case SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY ->
+                        mTraceThresholdFrameTimeMillis =
+                                properties.getInt(property, DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
+                case SETTINGS_ENABLED_KEY ->
+                        mEnabled = properties.getBoolean(property, DEFAULT_ENABLED);
+                case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY -> {
                     // Never allow the debug overlay to be used on user builds
                     boolean debugOverlayEnabled = Build.IS_DEBUGGABLE
                             && properties.getBoolean(property, DEFAULT_DEBUG_OVERLAY_ENABLED);
@@ -885,49 +546,35 @@
                         mDebugOverlay.dispose();
                         mDebugOverlay = null;
                     }
-                    break;
-                default:
-                    if (DEBUG) {
-                        Log.d(TAG, "Got a change event for an unknown property: "
-                                + property + " => " + properties.getString(property, ""));
-                    }
+                }
+                default -> Log.w(TAG, "Got a change event for an unknown property: "
+                        + property + " => " + properties.getString(property, ""));
             }
         }
     }
 
-    @VisibleForTesting
-    public DeviceConfig.OnPropertiesChangedListener getPropertiesChangedListener() {
-        return mPropertiesChangedListener;
-    }
-
-    /**
-     * Triggers the perfetto daemon to collect and upload data.
-     */
-    @VisibleForTesting
-    public void trigger(Session session) {
-        mWorker.getThreadHandler().post(
-                () -> PerfettoTrigger.trigger(session.getPerfettoTrigger()));
-    }
-
     /**
      * A helper method to translate interaction type to CUJ name.
      *
      * @param interactionType the interaction type defined in AtomsProto.java
      * @return the name of the interaction type
+     * @deprecated use {@link Cuj#getNameOfInteraction(int)}
      */
+    @Deprecated
     public static String getNameOfInteraction(int interactionType) {
-        // There is an offset amount of 1 between cujType and interactionType.
-        return getNameOfCuj(getCujTypeFromInteraction(interactionType));
+        return Cuj.getNameOfInteraction(interactionType);
     }
 
     /**
-     * A helper method to translate interaction type to CUJ type.
+     * A helper method to translate CUJ type to CUJ name.
      *
-     * @param interactionType the interaction type defined in AtomsProto.java
-     * @return the integer in {@link CujType}
+     * @param cujType the cuj type defined in this file
+     * @return the name of the cuj type
+     * @deprecated use {@link Cuj#getNameOfCuj(int)}
      */
-    private static int getCujTypeFromInteraction(int interactionType) {
-        return interactionType - 1;
+    @Deprecated
+    public static String getNameOfCuj(int cujType) {
+        return Cuj.getNameOfCuj(cujType);
     }
 
     /**
@@ -943,195 +590,14 @@
         mDebugYOffset = yOffset;
     }
 
-    /**
-     * A helper method for getting a string representation of all running CUJs. For example,
-     * "(LOCKSCREEN_TRANSITION_FROM_AOD, IME_INSETS_ANIMATION)"
-     */
-    private static String listNamesOfCujs(SparseArray<FrameTracker> trackers) {
-        if (!DEBUG) {
-            return null;
-        }
-        StringBuilder sb = new StringBuilder();
-        sb.append('(');
-        for (int i = 0; i < trackers.size(); i++) {
-            sb.append(getNameOfCuj(trackers.keyAt(i)));
-            if (i < trackers.size() - 1) {
-                sb.append(", ");
-            }
-        }
-        sb.append(')');
-        return sb.toString();
-    }
+    private void postEventLogToWorkerThread(TimeFunction logFunction) {
+        final Instant now = Instant.now();
+        final long unixNanos = TimeUnit.NANOSECONDS.convert(now.getEpochSecond(), TimeUnit.SECONDS)
+                + now.getNano();
+        final long elapsedNanos = SystemClock.elapsedRealtimeNanos();
+        final long realtimeNanos = SystemClock.uptimeNanos();
 
-    /**
-     * A helper method to translate CUJ type to CUJ name.
-     *
-     * @param cujType the cuj type defined in this file
-     * @return the name of the cuj type
-     */
-    public static String getNameOfCuj(int cujType) {
-        // Please note:
-        // 1. The length of the returned string shouldn't exceed MAX_LENGTH_OF_CUJ_NAME.
-        // 2. The returned string should be the same with the name defined in atoms.proto.
-        switch (cujType) {
-            case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE:
-                return "NOTIFICATION_SHADE_EXPAND_COLLAPSE";
-            case CUJ_NOTIFICATION_SHADE_SCROLL_FLING:
-                return "NOTIFICATION_SHADE_SCROLL_FLING";
-            case CUJ_NOTIFICATION_SHADE_ROW_EXPAND:
-                return "NOTIFICATION_SHADE_ROW_EXPAND";
-            case CUJ_NOTIFICATION_SHADE_ROW_SWIPE:
-                return "NOTIFICATION_SHADE_ROW_SWIPE";
-            case CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE:
-                return "NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE";
-            case CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE:
-                return "NOTIFICATION_SHADE_QS_SCROLL_SWIPE";
-            case CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS:
-                return "LAUNCHER_APP_LAUNCH_FROM_RECENTS";
-            case CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON:
-                return "LAUNCHER_APP_LAUNCH_FROM_ICON";
-            case CUJ_LAUNCHER_APP_CLOSE_TO_HOME:
-                return "LAUNCHER_APP_CLOSE_TO_HOME";
-            case CUJ_LAUNCHER_APP_CLOSE_TO_PIP:
-                return "LAUNCHER_APP_CLOSE_TO_PIP";
-            case CUJ_LAUNCHER_QUICK_SWITCH:
-                return "LAUNCHER_QUICK_SWITCH";
-            case CUJ_NOTIFICATION_HEADS_UP_APPEAR:
-                return "NOTIFICATION_HEADS_UP_APPEAR";
-            case CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR:
-                return "NOTIFICATION_HEADS_UP_DISAPPEAR";
-            case CUJ_NOTIFICATION_ADD:
-                return "NOTIFICATION_ADD";
-            case CUJ_NOTIFICATION_REMOVE:
-                return "NOTIFICATION_REMOVE";
-            case CUJ_NOTIFICATION_APP_START:
-                return "NOTIFICATION_APP_START";
-            case CUJ_LOCKSCREEN_PASSWORD_APPEAR:
-                return "LOCKSCREEN_PASSWORD_APPEAR";
-            case CUJ_LOCKSCREEN_PATTERN_APPEAR:
-                return "LOCKSCREEN_PATTERN_APPEAR";
-            case CUJ_LOCKSCREEN_PIN_APPEAR:
-                return "LOCKSCREEN_PIN_APPEAR";
-            case CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR:
-                return "LOCKSCREEN_PASSWORD_DISAPPEAR";
-            case CUJ_LOCKSCREEN_PATTERN_DISAPPEAR:
-                return "LOCKSCREEN_PATTERN_DISAPPEAR";
-            case CUJ_LOCKSCREEN_PIN_DISAPPEAR:
-                return "LOCKSCREEN_PIN_DISAPPEAR";
-            case CUJ_LOCKSCREEN_TRANSITION_FROM_AOD:
-                return "LOCKSCREEN_TRANSITION_FROM_AOD";
-            case CUJ_LOCKSCREEN_TRANSITION_TO_AOD:
-                return "LOCKSCREEN_TRANSITION_TO_AOD";
-            case CUJ_LAUNCHER_OPEN_ALL_APPS :
-                return "LAUNCHER_OPEN_ALL_APPS";
-            case CUJ_LAUNCHER_ALL_APPS_SCROLL:
-                return "LAUNCHER_ALL_APPS_SCROLL";
-            case CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET:
-                return "LAUNCHER_APP_LAUNCH_FROM_WIDGET";
-            case CUJ_SETTINGS_PAGE_SCROLL:
-                return "SETTINGS_PAGE_SCROLL";
-            case CUJ_LOCKSCREEN_UNLOCK_ANIMATION:
-                return "LOCKSCREEN_UNLOCK_ANIMATION";
-            case CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON:
-                return "SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON";
-            case CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER:
-                return "SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER";
-            case CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE:
-                return "SHADE_APP_LAUNCH_FROM_QS_TILE";
-            case CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON:
-                return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON";
-            case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP:
-                return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP";
-            case CUJ_PIP_TRANSITION:
-                return "PIP_TRANSITION";
-            case CUJ_WALLPAPER_TRANSITION:
-                return "WALLPAPER_TRANSITION";
-            case CUJ_USER_SWITCH:
-                return "USER_SWITCH";
-            case CUJ_SPLASHSCREEN_AVD:
-                return "SPLASHSCREEN_AVD";
-            case CUJ_SPLASHSCREEN_EXIT_ANIM:
-                return "SPLASHSCREEN_EXIT_ANIM";
-            case CUJ_SCREEN_OFF:
-                return "SCREEN_OFF";
-            case CUJ_SCREEN_OFF_SHOW_AOD:
-                return "SCREEN_OFF_SHOW_AOD";
-            case CUJ_ONE_HANDED_ENTER_TRANSITION:
-                return "ONE_HANDED_ENTER_TRANSITION";
-            case CUJ_ONE_HANDED_EXIT_TRANSITION:
-                return "ONE_HANDED_EXIT_TRANSITION";
-            case CUJ_UNFOLD_ANIM:
-                return "UNFOLD_ANIM";
-            case CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS:
-                return "SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS";
-            case CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS:
-                return "SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS";
-            case CUJ_SUW_LOADING_TO_NEXT_FLOW:
-                return "SUW_LOADING_TO_NEXT_FLOW";
-            case CUJ_SUW_LOADING_SCREEN_FOR_STATUS:
-                return "SUW_LOADING_SCREEN_FOR_STATUS";
-            case CUJ_SPLIT_SCREEN_ENTER:
-                return "SPLIT_SCREEN_ENTER";
-            case CUJ_SPLIT_SCREEN_EXIT:
-                return "SPLIT_SCREEN_EXIT";
-            case CUJ_LOCKSCREEN_LAUNCH_CAMERA:
-                return "LOCKSCREEN_LAUNCH_CAMERA";
-            case CUJ_SPLIT_SCREEN_RESIZE:
-                return "SPLIT_SCREEN_RESIZE";
-            case CUJ_SETTINGS_SLIDER:
-                return "SETTINGS_SLIDER";
-            case CUJ_TAKE_SCREENSHOT:
-                return "TAKE_SCREENSHOT";
-            case CUJ_VOLUME_CONTROL:
-                return "VOLUME_CONTROL";
-            case CUJ_BIOMETRIC_PROMPT_TRANSITION:
-                return "BIOMETRIC_PROMPT_TRANSITION";
-            case CUJ_SETTINGS_TOGGLE:
-                return "SETTINGS_TOGGLE";
-            case CUJ_SHADE_DIALOG_OPEN:
-                return "SHADE_DIALOG_OPEN";
-            case CUJ_USER_DIALOG_OPEN:
-                return "USER_DIALOG_OPEN";
-            case CUJ_TASKBAR_EXPAND:
-                return "TASKBAR_EXPAND";
-            case CUJ_TASKBAR_COLLAPSE:
-                return "TASKBAR_COLLAPSE";
-            case CUJ_SHADE_CLEAR_ALL:
-                return "SHADE_CLEAR_ALL";
-            case CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION:
-                return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION";
-            case CUJ_LOCKSCREEN_OCCLUSION:
-                return "LOCKSCREEN_OCCLUSION";
-            case CUJ_RECENTS_SCROLLING:
-                return "RECENTS_SCROLLING";
-            case CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS:
-                return "LAUNCHER_APP_SWIPE_TO_RECENTS";
-            case CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE:
-                return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
-            case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
-                return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
-            case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION:
-                return "LOCKSCREEN_CLOCK_MOVE_ANIMATION";
-            case CUJ_LAUNCHER_OPEN_SEARCH_RESULT:
-                return "LAUNCHER_OPEN_SEARCH_RESULT";
-            case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK:
-                return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK";
-            case CUJ_IME_INSETS_SHOW_ANIMATION:
-                return "IME_INSETS_SHOW_ANIMATION";
-            case CUJ_IME_INSETS_HIDE_ANIMATION:
-                return "IME_INSETS_HIDE_ANIMATION";
-            case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER:
-                return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER";
-            case CUJ_LAUNCHER_UNFOLD_ANIM:
-                return "LAUNCHER_UNFOLD_ANIM";
-            case CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY:
-                return "PREDICTIVE_BACK_CROSS_ACTIVITY";
-            case CUJ_PREDICTIVE_BACK_CROSS_TASK:
-                return "PREDICTIVE_BACK_CROSS_TASK";
-            case CUJ_PREDICTIVE_BACK_HOME:
-                return "PREDICTIVE_BACK_HOME";
-        }
-        return "UNKNOWN";
+        mWorker.post(() -> logFunction.invoke(unixNanos, elapsedNanos, realtimeNanos));
     }
 
     private static class TrackerResult {
@@ -1147,9 +613,10 @@
         private final Context mContext;
         private final long mTimeout;
         private final String mTag;
+        private final String mSessionName;
         private final boolean mSurfaceOnly;
         private final SurfaceControl mSurfaceControl;
-        private final @CujType int mCujType;
+        private final @Cuj.CujType int mCujType;
         private final boolean mDeferMonitor;
         private final Handler mHandler;
 
@@ -1167,17 +634,17 @@
             private String mAttrTag = "";
             private boolean mAttrSurfaceOnly;
             private SurfaceControl mAttrSurfaceControl;
-            private @CujType int mAttrCujType;
+            private final @Cuj.CujType int mAttrCujType;
             private boolean mAttrDeferMonitor = true;
 
             /**
              * Creates a builder which instruments only surface.
-             * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}.
+             * @param cuj The enum defined in {@link Cuj.CujType}.
              * @param context context
              * @param surfaceControl surface control
              * @return builder
              */
-            public static Builder withSurface(@CujType int cuj, @NonNull Context context,
+            public static Builder withSurface(@Cuj.CujType int cuj, @NonNull Context context,
                     @NonNull SurfaceControl surfaceControl) {
                 return new Builder(cuj)
                         .setContext(context)
@@ -1187,16 +654,17 @@
 
             /**
              * Creates a builder which instruments both surface and view.
-             * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}.
+             * @param cuj The enum defined in {@link Cuj.CujType}.
              * @param view view
              * @return builder
              */
-            public static Builder withView(@CujType int cuj, @NonNull View view) {
-                return new Builder(cuj).setView(view)
+            public static Builder withView(@Cuj.CujType int cuj, @NonNull View view) {
+                return new Builder(cuj)
+                        .setView(view)
                         .setContext(view.getContext());
             }
 
-            private Builder(@CujType int cuj) {
+            private Builder(@Cuj.CujType int cuj) {
                 mAttrCujType = cuj;
             }
 
@@ -1281,11 +749,12 @@
             }
         }
 
-        private Configuration(@CujType int cuj, View view, String tag, long timeout,
+        private Configuration(@Cuj.CujType int cuj, View view, @NonNull String tag, long timeout,
                 boolean surfaceOnly, Context context, SurfaceControl surfaceControl,
                 boolean deferMonitor) {
             mCujType = cuj;
             mTag = tag;
+            mSessionName = generateSessionName(Cuj.getNameOfCuj(cuj), tag);
             mTimeout = timeout;
             mView = view;
             mSurfaceOnly = surfaceOnly;
@@ -1298,6 +767,23 @@
             mHandler = mSurfaceOnly ? mContext.getMainThreadHandler() : mView.getHandler();
         }
 
+        @VisibleForTesting
+        public static String generateSessionName(
+                @NonNull String cujName, @NonNull String cujPostfix) {
+            final boolean hasPostfix = !TextUtils.isEmpty(cujPostfix);
+            if (hasPostfix) {
+                final int remaining = MAX_LENGTH_SESSION_NAME - cujName.length();
+                if (cujPostfix.length() > remaining) {
+                    cujPostfix = cujPostfix.substring(0, remaining - 3).concat("...");
+                }
+            }
+            // The max length of the whole string should be:
+            // 105 with postfix, 83 without postfix
+            return hasPostfix
+                    ? TextUtils.formatSimple("J<%s::%s>", cujName, cujPostfix)
+                    : TextUtils.formatSimple("J<%s>", cujName);
+        }
+
         private void validate() {
             boolean shouldThrow = false;
             final StringBuilder msg = new StringBuilder();
@@ -1360,10 +846,10 @@
             return mSurfaceControl;
         }
 
-        @VisibleForTesting
         /**
          * @return a view which is attached to the view tree.
          */
+        @VisibleForTesting
         public View getView() {
             return mView;
         }
@@ -1375,7 +861,7 @@
             return mDeferMonitor;
         }
 
-        @VisibleForTesting
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
         public Handler getHandler() {
             return mHandler;
         }
@@ -1387,79 +873,27 @@
         public int getDisplayId() {
             return (mSurfaceOnly ? mContext : mView.getContext()).getDisplayId();
         }
-    }
 
-    /**
-     * A class to represent a session.
-     */
-    public static class Session {
-        @CujType
-        private final int mCujType;
-        private final long mTimeStamp;
-        @Reasons
-        private int mReason = REASON_END_UNKNOWN;
-        private final String mName;
-
-        public Session(@CujType int cujType, @NonNull String postfix) {
-            mCujType = cujType;
-            mTimeStamp = System.nanoTime();
-            mName = generateSessionName(getNameOfCuj(cujType), postfix);
-        }
-
-        private String generateSessionName(@NonNull String cujName, @NonNull String cujPostfix) {
-            final boolean hasPostfix = !TextUtils.isEmpty(cujPostfix);
-            // We assert that the cujName shouldn't exceed MAX_LENGTH_OF_CUJ_NAME.
-            if (cujName.length() > MAX_LENGTH_OF_CUJ_NAME) {
-                throw new IllegalArgumentException(TextUtils.formatSimple(
-                        "The length of cuj name <%s> exceeds %d", cujName, MAX_LENGTH_OF_CUJ_NAME));
-            }
-            if (hasPostfix) {
-                final int remaining = MAX_LENGTH_SESSION_NAME - cujName.length();
-                if (cujPostfix.length() > remaining) {
-                    cujPostfix = cujPostfix.substring(0, remaining - 3).concat("...");
-                }
-            }
-            // The max length of the whole string should be:
-            // 105 with postfix, 83 without postfix
-            return hasPostfix
-                    ? TextUtils.formatSimple("J<%s::%s>", cujName, cujPostfix)
-                    : TextUtils.formatSimple("J<%s>", cujName);
-        }
-
-        @CujType
-        public int getCuj() {
-            return mCujType;
+        public String getSessionName() {
+            return mSessionName;
         }
 
         public int getStatsdInteractionType() {
-            return CUJ_TO_STATSD_INTERACTION_TYPE[mCujType];
+            return Cuj.getStatsdInteractionType(mCujType);
         }
 
         /** Describes whether the measurement from this session should be written to statsd. */
         public boolean logToStatsd() {
-            return getStatsdInteractionType() != NO_STATSD_LOGGING;
+            return Cuj.logToStatsd(mCujType);
         }
 
         public String getPerfettoTrigger() {
-            return String.format(Locale.US, "com.android.telemetry.interaction-jank-monitor-%d",
-                    mCujType);
+            return TextUtils.formatSimple(
+                    "com.android.telemetry.interaction-jank-monitor-%d", mCujType);
         }
 
-        public String getName() {
-            return mName;
-        }
-
-        public long getTimeStamp() {
-            return mTimeStamp;
-        }
-
-        public void setReason(@Reasons int reason) {
-            mReason = reason;
-        }
-
-        @Reasons
-        public int getReason() {
-            return mReason;
+        public @Cuj.CujType int getCujType() {
+            return mCujType;
         }
     }
 
@@ -1468,15 +902,15 @@
         void invoke(long unixNanos, long elapsedNanos, long realtimeNanos);
     }
 
-    private void postEventLogToWorkerThread(TimeFunction logFunction) {
-        final Instant now = Instant.now();
-        final long unixNanos = TimeUnit.NANOSECONDS.convert(now.getEpochSecond(), TimeUnit.SECONDS)
-                + now.getNano();
-        final long elapsedNanos = SystemClock.elapsedRealtimeNanos();
-        final long realtimeNanos = SystemClock.uptimeNanos();
+    static class RunningTracker {
+        public final Configuration mConfig;
+        public final FrameTracker mTracker;
+        public final Runnable mTimeoutAction;
 
-        mWorker.getThreadHandler().post(() -> {
-            logFunction.invoke(unixNanos, elapsedNanos, realtimeNanos);
-        });
+        RunningTracker(Configuration config, FrameTracker tracker, Runnable timeoutAction) {
+            this.mConfig = config;
+            this.mTracker = tracker;
+            this.mTimeoutAction = timeoutAction;
+        }
     }
 }
diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
index ef7944c..f3f16a0 100644
--- a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
+++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
@@ -34,7 +34,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.jank.FrameTracker.Reasons;
-import com.android.internal.jank.InteractionJankMonitor.CujType;
 
 /**
  * An overlay that uses WindowCallbacks to draw the names of all running CUJs to the window
@@ -94,14 +93,14 @@
     }
 
     @UiThread
-    private boolean attachViewRootIfNeeded(FrameTracker tracker) {
-        FrameTracker.ViewRootWrapper viewRoot = tracker.getViewRoot();
+    private boolean attachViewRootIfNeeded(InteractionJankMonitor.RunningTracker tracker) {
+        FrameTracker.ViewRootWrapper viewRoot = tracker.mTracker.getViewRoot();
         if (mViewRoot == null && viewRoot != null) {
             // Add a trace marker so we can identify traces that were captured while the debug
             // overlay was enabled. Traces that use the debug overlay should NOT be used for
             // performance analysis.
             Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, "DEBUG_OVERLAY_DRAW", 0);
-            mHandler = tracker.getHandler();
+            mHandler = tracker.mConfig.getHandler();
             mViewRoot = viewRoot;
             mHandler.runWithScissors(() -> viewRoot.addWindowCallbacks(this),
                     InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
@@ -111,11 +110,12 @@
         return false;
     }
 
+    @GuardedBy("mLock")
     private float getWidthOfLongestCujName(int cujFontSize) {
         mDebugPaint.setTextSize(cujFontSize);
         float maxLength = 0;
         for (int i = 0; i < mRunningCujs.size(); i++) {
-            String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i));
+            String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i));
             float textLength = mDebugPaint.measureText(cujName);
             if (textLength > maxLength) {
                 maxLength = textLength;
@@ -149,8 +149,8 @@
     }
 
     @UiThread
-    void onTrackerRemoved(@CujType int removedCuj, @Reasons int reason,
-                          SparseArray<FrameTracker> runningTrackers) {
+    void onTrackerRemoved(@Cuj.CujType int removedCuj, @Reasons int reason,
+                          SparseArray<InteractionJankMonitor.RunningTracker> runningTrackers) {
         synchronized (mLock) {
             mRunningCujs.put(removedCuj, reason);
             // If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
@@ -164,7 +164,7 @@
                     // trackers
                     for (int i = 0; i < runningTrackers.size(); i++) {
                         if (mViewRoot.equals(
-                                runningTrackers.valueAt(i).getViewRoot())) {
+                                runningTrackers.valueAt(i).mTracker.getViewRoot())) {
                             needsNewViewRoot = false;
                             break;
                         }
@@ -185,7 +185,7 @@
     }
 
     @UiThread
-    void onTrackerAdded(@CujType int addedCuj, FrameTracker tracker) {
+    void onTrackerAdded(@Cuj.CujType int addedCuj, InteractionJankMonitor.RunningTracker tracker) {
         synchronized (mLock) {
             // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
             // is still running
@@ -230,41 +230,44 @@
         int cujFontSize = dipToPx(18);
         final float cujNameTextHeight = getTextHeight(cujFontSize);
         final float packageNameTextHeight = getTextHeight(packageNameFontSize);
-        float maxLength = getWidthOfLongestCujName(cujFontSize);
 
-        final int dx = (int) ((w - maxLength) / 2f);
-        canvas.translate(dx, dy);
-        // Draw background rectangle for displaying the text showing the CUJ name
-        mDebugPaint.setColor(mBgColor);
-        canvas.drawRect(
-                -padding * 2, // more padding on top so we can draw the package name
-                -padding,
-                padding * 2 + maxLength,
-                padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(),
-                mDebugPaint);
-        mDebugPaint.setTextSize(packageNameFontSize);
-        mDebugPaint.setColor(Color.BLACK);
-        mDebugPaint.setStrikeThruText(false);
-        canvas.translate(0, packageNameTextHeight);
-        canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint);
-        mDebugPaint.setTextSize(cujFontSize);
-        // Draw text for CUJ names
-        for (int i = 0; i < mRunningCujs.size(); i++) {
-            int status = mRunningCujs.valueAt(i);
-            if (status == REASON_STILL_RUNNING) {
-                mDebugPaint.setColor(Color.BLACK);
-                mDebugPaint.setStrikeThruText(false);
-            } else if (status == REASON_END_NORMAL) {
-                mDebugPaint.setColor(Color.GRAY);
-                mDebugPaint.setStrikeThruText(false);
-            } else {
-                // Cancelled, or otherwise ended for a bad reason
-                mDebugPaint.setColor(Color.RED);
-                mDebugPaint.setStrikeThruText(true);
+        synchronized (mLock) {
+            float maxLength = getWidthOfLongestCujName(cujFontSize);
+
+            final int dx = (int) ((w - maxLength) / 2f);
+            canvas.translate(dx, dy);
+            // Draw background rectangle for displaying the text showing the CUJ name
+            mDebugPaint.setColor(mBgColor);
+            canvas.drawRect(
+                    -padding * 2, // more padding on top so we can draw the package name
+                    -padding,
+                    padding * 2 + maxLength,
+                    padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(),
+                    mDebugPaint);
+            mDebugPaint.setTextSize(packageNameFontSize);
+            mDebugPaint.setColor(Color.BLACK);
+            mDebugPaint.setStrikeThruText(false);
+            canvas.translate(0, packageNameTextHeight);
+            canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint);
+            mDebugPaint.setTextSize(cujFontSize);
+            // Draw text for CUJ names
+            for (int i = 0; i < mRunningCujs.size(); i++) {
+                int status = mRunningCujs.valueAt(i);
+                if (status == REASON_STILL_RUNNING) {
+                    mDebugPaint.setColor(Color.BLACK);
+                    mDebugPaint.setStrikeThruText(false);
+                } else if (status == REASON_END_NORMAL) {
+                    mDebugPaint.setColor(Color.GRAY);
+                    mDebugPaint.setStrikeThruText(false);
+                } else {
+                    // Cancelled, or otherwise ended for a bad reason
+                    mDebugPaint.setColor(Color.RED);
+                    mDebugPaint.setStrikeThruText(true);
+                }
+                String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i));
+                canvas.translate(0, cujNameTextHeight);
+                canvas.drawText(cujName, 0, 0, mDebugPaint);
             }
-            String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i));
-            canvas.translate(0, cujNameTextHeight);
-            canvas.drawText(cujName, 0, 0, mDebugPaint);
         }
     }
 }
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 1f44b33..ed943cb 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -55,15 +55,20 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+        "com.android.hoststubgen.nativesubstitution.LongArrayMultiStateCounter_host")
 public final class LongArrayMultiStateCounter implements Parcelable {
 
     /**
      * Container for a native equivalent of a long[].
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
+    @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+            "com.android.hoststubgen.nativesubstitution"
+            + ".LongArrayMultiStateCounter_host$LongArrayContainer_host")
     public static class LongArrayContainer {
-        private static final NativeAllocationRegistry sRegistry =
-                NativeAllocationRegistry.createMalloced(
-                        LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());
+        private static NativeAllocationRegistry sRegistry;
 
         // Visible to other objects in this package so that it can be passed to @CriticalNative
         // methods.
@@ -73,9 +78,26 @@
         public LongArrayContainer(int length) {
             mLength = length;
             mNativeObject = native_init(length);
+            registerNativeAllocation();
+        }
+
+        @android.ravenwood.annotation.RavenwoodReplace
+        private void registerNativeAllocation() {
+            if (sRegistry == null) {
+                synchronized (LongArrayMultiStateCounter.class) {
+                    if (sRegistry == null) {
+                        sRegistry = NativeAllocationRegistry.createMalloced(
+                                LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());
+                    }
+                }
+            }
             sRegistry.registerNativeAllocation(this, mNativeObject);
         }
 
+        private void registerNativeAllocation$ravenwood() {
+            // No-op under ravenwood
+        }
+
         /**
          * Copies the supplied values into the underlying native array.
          */
@@ -124,19 +146,17 @@
         private static native long native_getReleaseFunc();
 
         @FastNative
-        private native void native_setValues(long nativeObject, long[] array);
+        private static native void native_setValues(long nativeObject, long[] array);
 
         @FastNative
-        private native void native_getValues(long nativeObject, long[] array);
+        private static native void native_getValues(long nativeObject, long[] array);
 
         @FastNative
-        private native boolean native_combineValues(long nativeObject, long[] array,
+        private static native boolean native_combineValues(long nativeObject, long[] array,
                 int[] indexMap);
     }
 
-    private static final NativeAllocationRegistry sRegistry =
-            NativeAllocationRegistry.createMalloced(
-                    LongArrayMultiStateCounter.class.getClassLoader(), native_getReleaseFunc());
+    private static volatile NativeAllocationRegistry sRegistry;
     private static final AtomicReference<LongArrayContainer> sTmpArrayContainer =
             new AtomicReference<>();
 
@@ -152,12 +172,30 @@
         mStateCount = stateCount;
         mLength = arrayLength;
         mNativeObject = native_init(stateCount, arrayLength);
+        registerNativeAllocation();
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private void registerNativeAllocation() {
+        if (sRegistry == null) {
+            synchronized (LongArrayMultiStateCounter.class) {
+                if (sRegistry == null) {
+                    sRegistry = NativeAllocationRegistry.createMalloced(
+                            LongArrayMultiStateCounter.class.getClassLoader(),
+                            native_getReleaseFunc());
+                }
+            }
+        }
         sRegistry.registerNativeAllocation(this, mNativeObject);
     }
 
+    private void registerNativeAllocation$ravenwood() {
+        // No-op under ravenwood
+    }
+
     private LongArrayMultiStateCounter(Parcel in) {
         mNativeObject = native_initFromParcel(in);
-        sRegistry.registerNativeAllocation(this, mNativeObject);
+        registerNativeAllocation();
 
         mStateCount = native_getStateCount(mNativeObject);
         mLength = native_getArrayLength(mNativeObject);
@@ -361,10 +399,10 @@
             long longArrayContainerNativeObject, int state);
 
     @FastNative
-    private native String native_toString(long nativeObject);
+    private static native String native_toString(long nativeObject);
 
     @FastNative
-    private native void native_writeToParcel(long nativeObject, Parcel dest, int flags);
+    private static native void native_writeToParcel(long nativeObject, Parcel dest, int flags);
 
     @FastNative
     private static native long native_initFromParcel(Parcel parcel);
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index 4ed361f..6c09b7c 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -112,7 +112,7 @@
     ParsingPackage addUsesOptionalNativeLibrary(String libraryName);
 
     ParsingPackage addUsesSdkLibrary(String libraryName, long versionMajor,
-            String[] certSha256Digests);
+            String[] certSha256Digests, boolean usesSdkLibrariesOptional);
 
     ParsingPackage addUsesStaticLibrary(String libraryName, long version,
             String[] certSha256Digests);
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 4bb7c33..8c2a525 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -93,6 +93,7 @@
     WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
 
     WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
+    WM_DEBUG_TPL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
     TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index 4e4f26c..f86595f 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -1340,6 +1340,15 @@
     @Nullable
     long[] getUsesSdkLibrariesVersionsMajor();
 
+
+    /**
+     * @see R.styleable#AndroidManifestUsesSdkLibrary_optional
+     * @hide
+     */
+    @Immutable.Ignore
+    @Nullable
+    boolean[] getUsesSdkLibrariesOptional();
+
     /**
      * TODO(b/135203078): Move static library stuff to an inner data class
      *
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f365dbb..2a744e3 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -49,8 +49,6 @@
         "-Wno-unused-parameter",
         "-Wunused",
         "-Wunreachable-code",
-
-        "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
     ],
 
     cppflags: ["-Wno-conversion-null"],
@@ -284,8 +282,6 @@
                 "libscrypt_static",
                 "libstatssocket_lazy",
                 "libskia",
-                "libtextclassifier_hash_static",
-                "libexpresslog_jni",
             ],
 
             shared_libs: [
@@ -372,7 +368,6 @@
                 "bionic_libc_platform_headers",
                 "dnsproxyd_protocol_headers",
                 "flatbuffer_headers",
-                "libtextclassifier_hash_headers",
                 "tensorflow_headers",
             ],
             runtime_libs: [
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 50253cf..c24d21d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -201,7 +201,6 @@
 extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
 extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
 extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env);
-extern int register_com_android_modules_expresslog_Utils(JNIEnv* env);
 extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
 extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
@@ -1590,7 +1589,6 @@
         REG_JNI(register_android_os_incremental_IncrementalManager),
         REG_JNI(register_com_android_internal_content_om_OverlayConfig),
         REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl),
-        REG_JNI(register_com_android_modules_expresslog_Utils),
         REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
         REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
         REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index e0bcef6..de1ce4e 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -1014,6 +1014,10 @@
     return cfg_state == CONFIG_SET;
 }
 
+static jboolean android_os_Debug_logAllocatorStats(JNIEnv*, jobject) {
+    return mallopt(M_LOG_STATS, 0) == 1 ? JNI_TRUE : JNI_FALSE;
+}
+
 /*
  * JNI registration.
  */
@@ -1056,6 +1060,7 @@
         {"getDmabufHeapPoolsSizeKb", "()J", (void*)android_os_Debug_getDmabufHeapPoolsSizeKb},
         {"getGpuTotalUsageKb", "()J", (void*)android_os_Debug_getGpuTotalUsageKb},
         {"isVmapStack", "()Z", (void*)android_os_Debug_isVmapStack},
+        {"logAllocatorStats", "()Z", (void*)android_os_Debug_logAllocatorStats},
 };
 
 int register_android_os_Debug(JNIEnv *env)
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 43e0c34..91dfc60 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1238,7 +1238,7 @@
     return killProcessGroup(uid, pid, SIGKILL);
 }
 
-jboolean android_os_Process_sendSignalToProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid,
+jint android_os_Process_sendSignalToProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid,
                                                  jint signal) {
     return sendSignalToProcessGroup(uid, pid, signal);
 }
@@ -1310,7 +1310,7 @@
         //{"setApplicationObject", "(Landroid/os/IBinder;)V",
         //(void*)android_os_Process_setApplicationObject},
         {"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup},
-        {"sendSignalToProcessGroup", "(III)Z", (void*)android_os_Process_sendSignalToProcessGroup},
+        {"sendSignalToProcessGroup", "(III)I", (void*)android_os_Process_sendSignalToProcessGroup},
         {"removeAllProcessGroups", "()V", (void*)android_os_Process_removeAllProcessGroups},
         {"nativePidFdOpen", "(II)I", (void*)android_os_Process_nativePidFdOpen},
         {"freezeCgroupUid", "(IZ)V", (void*)android_os_Process_freezeCgroupUID},
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index dab47e9..76b05ea 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -107,7 +107,7 @@
     *vector = counter->getCount(state);
 }
 
-static jobject native_toString(JNIEnv *env, jobject self, jlong nativePtr) {
+static jobject native_toString(JNIEnv *env, jclass, jlong nativePtr) {
     battery::LongArrayMultiStateCounter *counter =
             reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
     return env->NewStringUTF(counter->toString().c_str());
@@ -127,7 +127,7 @@
         }                                     \
     }
 
-static void native_writeToParcel(JNIEnv *env, jobject self, jlong nativePtr, jobject jParcel,
+static void native_writeToParcel(JNIEnv *env, jclass, jlong nativePtr, jobject jParcel,
                                  jint flags) {
     battery::LongArrayMultiStateCounter *counter =
             reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
@@ -161,7 +161,7 @@
         }                                    \
     }
 
-static jlong native_initFromParcel(JNIEnv *env, jclass theClass, jobject jParcel) {
+static jlong native_initFromParcel(JNIEnv *env, jclass, jobject jParcel) {
     ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel));
 
     int32_t stateCount;
@@ -253,7 +253,7 @@
     return reinterpret_cast<jlong>(native_dispose_LongArrayContainer);
 }
 
-static void native_setValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
+static void native_setValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
                                                 jlongArray jarray) {
     std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
     ScopedLongArrayRO scopedArray(env, jarray);
@@ -264,7 +264,7 @@
     std::copy(array, array + size, vector->data());
 }
 
-static void native_getValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
+static void native_getValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
                                                 jlongArray jarray) {
     std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
     ScopedLongArrayRW scopedArray(env, jarray);
@@ -273,7 +273,7 @@
     std::copy(vector->data(), vector->data() + vector->size(), scopedArray.get());
 }
 
-static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
+static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
                                                         jlongArray jarray, jintArray jindexMap) {
     std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
     ScopedLongArrayRW scopedArray(env, jarray);
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index f8546b7..596cfe5 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2515,6 +2515,10 @@
         <attr name="versionMajor" format="integer" />
         <!-- The SHA-256 digest of the SDK library signing certificate. -->
         <attr name="certDigest" format="string" />
+        <!-- Specify whether the SDK is optional. The default is false, false means app can be
+        installed even if the SDK library doesn't exist, and the SDK library can be uninstalled
+        when the app is still installed. -->
+        <attr name="optional" format="boolean" />
     </declare-styleable>
 
     <!-- The <code>static-library</code> tag declares that this apk is providing itself
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index b12f302..f10e7f8 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -115,6 +115,8 @@
     <!-- @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime")
          @hide @SystemApi -->
     <public name="isVirtualDeviceOnly"/>
+    <!-- @FlaggedApi("android.content.pm.sdk_lib_independence") -->
+    <public name="optional"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 88abaa1..6706c91 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -185,6 +185,7 @@
         "src/android/os/BuildTest.java",
         "src/android/os/FileUtilsTest.java",
         "src/android/util/**/*.java",
+        "src/com/android/internal/os/LongArrayMultiStateCounterTest.java",
         "src/com/android/internal/util/**/*.java",
         "testdoubles/src/com/android/internal/util/**/*.java",
     ],
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 7c4136d..9300d1e 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -831,6 +831,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 299483542)
     public void onPropertiesChangedListener_setPropertiesCallback() {
         DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
         DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false);
diff --git a/core/tests/coretests/src/com/android/internal/jank/CujTest.java b/core/tests/coretests/src/com/android/internal/jank/CujTest.java
new file mode 100644
index 0000000..bf35ed0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/jank/CujTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.internal.jank;
+
+import static android.text.TextUtils.formatSimple;
+
+import static com.android.internal.jank.Cuj.getNameOfCuj;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@SmallTest
+public class CujTest {
+    private static final String ENUM_NAME_PREFIX =
+            "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__";
+    private static final Set<String> DEPRECATED_VALUES = new HashSet<>() {
+        {
+            add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION");
+        }
+    };
+    private static final Map<Integer, String> ENUM_NAME_EXCEPTION_MAP = new HashMap<>() {
+        {
+            put(Cuj.CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD"));
+            put(Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR"));
+            put(Cuj.CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH"));
+            put(Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, getEnumName("SHADE_HEADS_UP_DISAPPEAR"));
+            put(Cuj.CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE"));
+            put(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, getEnumName("NOTIFICATION_SHADE_SWIPE"));
+            put(Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, getEnumName("SHADE_QS_EXPAND_COLLAPSE"));
+            put(Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, getEnumName("SHADE_QS_SCROLL_SWIPE"));
+            put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND"));
+            put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE"));
+            put(Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING"));
+        }
+    };
+
+    @Rule
+    public final Expect mExpect = Expect.create();
+
+    @Test
+    public void testCujNameLimit() {
+        getCujConstants().forEach(f -> {
+            final int cuj = getIntFieldChecked(f);
+            mExpect.withMessage(formatSimple("Too long CUJ(%d) name: %s", cuj, getNameOfCuj(cuj)))
+                    .that(getNameOfCuj(cuj).length())
+                    .isAtMost(Cuj.MAX_LENGTH_OF_CUJ_NAME);
+        });
+    }
+
+    @Test
+    public void testCujTypeEnumCorrectlyDefined() throws Exception {
+        List<Field> cujEnumFields = getCujConstants().toList();
+
+        HashSet<Integer> allValues = new HashSet<>();
+        for (Field field : cujEnumFields) {
+            int fieldValue = field.getInt(null);
+            assertWithMessage("All CujType values must be unique. Field %s repeats existing value.",
+                    field.getName())
+                    .that(allValues.add(fieldValue))
+                    .isTrue();
+            assertWithMessage("Field %s must have a value <= LAST_CUJ", field.getName())
+                    .that(fieldValue)
+                    .isAtMost(Cuj.LAST_CUJ);
+            assertWithMessage("Field %s must have a statsd mapping.", field.getName())
+                    .that(Cuj.logToStatsd(fieldValue))
+                    .isTrue();
+        }
+    }
+
+    @Test
+    public void testCujsMapToEnumsCorrectly() {
+        List<Field> cujs = getCujConstants().toList();
+
+        Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
+                .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
+                        && !DEPRECATED_VALUES.contains(f.getName())
+                        && Modifier.isStatic(f.getModifiers())
+                        && f.getType() == int.class)
+                .collect(Collectors.toMap(CujTest::getIntFieldChecked, Field::getName));
+
+        assertThat(enumsMap.size() - 1).isEqualTo(cujs.size());
+
+        cujs.forEach(f -> {
+            final int cuj = getIntFieldChecked(f);
+            final String cujName = f.getName();
+            final String expectedEnumName =
+                    ENUM_NAME_EXCEPTION_MAP.getOrDefault(cuj, getEnumName(cujName.substring(4)));
+            final int enumKey = Cuj.getStatsdInteractionType(cuj);
+            final String enumName = enumsMap.get(enumKey);
+            final String expectedNameOfCuj = formatSimple("CUJ_%s", getNameOfCuj(cuj));
+
+            mExpect.withMessage(
+                    formatSimple("%s (%d) not matches %s (%d)", cujName, cuj, enumName, enumKey))
+                    .that(expectedEnumName.equals(enumName))
+                    .isTrue();
+            mExpect.withMessage(
+                    formatSimple("getNameOfCuj(%d) not matches: %s, expected=%s",
+                            cuj, cujName, expectedNameOfCuj))
+                    .that(cujName.equals(expectedNameOfCuj))
+                    .isTrue();
+        });
+    }
+
+    private static String getEnumName(String name) {
+        return formatSimple("%s%s", ENUM_NAME_PREFIX, name);
+    }
+
+    private static int getIntFieldChecked(Field field) {
+        try {
+            return field.getInt(null);
+        } catch (IllegalAccessException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private static Stream<Field> getCujConstants() {
+        return Arrays.stream(Cuj.class.getDeclaredFields())
+                .filter(f -> f.getName().startsWith("CUJ_")
+                        && Modifier.isStatic(f.getModifiers())
+                        && f.getType() == int.class);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index 1b9717a..1a7117e 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -22,9 +22,8 @@
 
 import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
 import static com.android.internal.jank.FrameTracker.ViewRootWrapper;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_WALLPAPER_TRANSITION;
+import static com.android.internal.jank.Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
+import static com.android.internal.jank.Cuj.CUJ_WALLPAPER_TRANSITION;
 import static com.android.internal.util.FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -49,15 +48,14 @@
 import android.view.View;
 import android.view.ViewAttachTestActivity;
 
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.filters.SmallTest;
-import androidx.test.rule.ActivityTestRule;
 
 import com.android.internal.jank.FrameTracker.ChoreographerWrapper;
 import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
 import com.android.internal.jank.FrameTracker.StatsLogWrapper;
 import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
-import com.android.internal.jank.InteractionJankMonitor.Session;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -69,14 +67,14 @@
 
 @SmallTest
 public class FrameTrackerTest {
-    private static final String CUJ_POSTFIX = "";
+    private static final String SESSION_NAME = "SessionName";
     private static final long FRAME_TIME_60Hz = (long) 1e9 / 60;
 
     private ViewAttachTestActivity mActivity;
 
     @Rule
-    public ActivityTestRule<ViewAttachTestActivity> mRule =
-            new ActivityTestRule<>(ViewAttachTestActivity.class);
+    public ActivityScenarioRule<ViewAttachTestActivity> mRule =
+            new ActivityScenarioRule<>(ViewAttachTestActivity.class);
 
     private ThreadedRendererWrapper mRenderer;
     private FrameMetricsWrapper mWrapper;
@@ -86,12 +84,13 @@
     private StatsLogWrapper mStatsLog;
     private ArgumentCaptor<OnJankDataListener> mListenerCapture;
     private SurfaceControl mSurfaceControl;
+    private FrameTracker.FrameTrackerListener mTrackerListener;
     private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
 
     @Before
     public void setup() {
         // Prepare an activity for getting ThreadedRenderer later.
-        mActivity = mRule.getActivity();
+        mRule.getScenario().onActivity(activity -> mActivity = activity);
         View view = mActivity.getWindow().getDecorView();
         assertThat(view.isAttachedToWindow()).isTrue();
 
@@ -116,36 +115,38 @@
         mChoreographer = mock(ChoreographerWrapper.class);
         mStatsLog = mock(StatsLogWrapper.class);
         mRunnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+        mTrackerListener = mock(FrameTracker.FrameTrackerListener.class);
     }
 
-    private FrameTracker spyFrameTracker(int cuj, String postfix, boolean surfaceOnly) {
-        InteractionJankMonitor monitor = mock(InteractionJankMonitor.class);
-        Handler handler = mRule.getActivity().getMainThreadHandler();
-        Session session = new Session(cuj, postfix);
+    private FrameTracker spyFrameTracker(boolean surfaceOnly) {
+        Handler handler = mActivity.getMainThreadHandler();
         Configuration config = mock(Configuration.class);
+        when(config.getSessionName()).thenReturn(SESSION_NAME);
         when(config.isSurfaceOnly()).thenReturn(surfaceOnly);
         when(config.getSurfaceControl()).thenReturn(mSurfaceControl);
         when(config.shouldDeferMonitor()).thenReturn(true);
         when(config.getDisplayId()).thenReturn(42);
-        View view = mRule.getActivity().getWindow().getDecorView();
+        View view = mActivity.getWindow().getDecorView();
         Handler spyHandler = spy(new Handler(handler.getLooper()));
         when(config.getView()).thenReturn(surfaceOnly ? null : view);
         when(config.getHandler()).thenReturn(spyHandler);
+        when(config.logToStatsd()).thenReturn(true);
+        when(config.getStatsdInteractionType()).thenReturn(surfaceOnly
+                ? Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)
+                : Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE));
         FrameTracker frameTracker = Mockito.spy(
-                new FrameTracker(monitor, session, spyHandler, mRenderer, mViewRootWrapper,
+                new FrameTracker(config, mRenderer, mViewRootWrapper,
                         mSurfaceControlWrapper, mChoreographer, mWrapper, mStatsLog,
                         /* traceThresholdMissedFrames= */ 1,
                         /* traceThresholdFrameTimeMillis= */ -1,
-                        /* FrameTrackerListener= */ null, config));
-        doNothing().when(frameTracker).triggerPerfetto();
+                        mTrackerListener));
         doNothing().when(frameTracker).postTraceStartMarker(mRunnableArgumentCaptor.capture());
         return frameTracker;
     }
 
     @Test
     public void testOnlyFirstWindowFrameOverThreshold() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         // Just provide current timestamp anytime mWrapper asked for VSYNC_TIMESTAMP
         when(mWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP))
@@ -169,11 +170,11 @@
         sendFrame(tracker, 500, JANK_APP_DEADLINE_MISSED, 103L);
 
         verify(tracker).removeObservers();
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+                eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
                 eq(5000000L) /* maxFrameTimeNanos */,
@@ -184,8 +185,7 @@
 
     @Test
     public void testSfJank() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -206,12 +206,12 @@
         verify(tracker).removeObservers();
 
         // We detected a janky frame - trigger Perfetto
-        verify(tracker).triggerPerfetto();
+        verify(mTrackerListener).triggerPerfetto(any());
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+                eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
                 eq(2L) /* totalFrames */,
                 eq(1L) /* missedFrames */,
                 eq(40000000L) /* maxFrameTimeNanos */,
@@ -222,8 +222,7 @@
 
     @Test
     public void testFirstFrameJankyNoTrigger() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -243,13 +242,12 @@
 
         verify(tracker).removeObservers();
 
-        // We detected a janky frame - trigger Perfetto
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+                eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
                 eq(4000000L) /* maxFrameTimeNanos */,
@@ -260,8 +258,7 @@
 
     @Test
     public void testOtherFrameOverThreshold() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -282,12 +279,12 @@
         verify(tracker).removeObservers();
 
         // We detected a janky frame - trigger Perfetto
-        verify(tracker).triggerPerfetto();
+        verify(mTrackerListener).triggerPerfetto(any());
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+                eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
                 eq(2L) /* totalFrames */,
                 eq(1L) /* missedFrames */,
                 eq(40000000L) /* maxFrameTimeNanos */,
@@ -298,8 +295,7 @@
 
     @Test
     public void testLastFrameOverThresholdBeforeEnd() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -323,12 +319,12 @@
         verify(tracker).removeObservers();
 
         // We detected a janky frame - trigger Perfetto
-        verify(tracker).triggerPerfetto();
+        verify(mTrackerListener).triggerPerfetto(any());
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+                eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
                 eq(2L) /* totalFrames */,
                 eq(1L) /* missedFrames */,
                 eq(50000000L) /* maxFrameTimeNanos */,
@@ -342,8 +338,7 @@
      */
     @Test
     public void testNoOvercountingAfterEnd() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -367,11 +362,11 @@
         sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 103L);
 
         verify(tracker).removeObservers();
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+                eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
                 eq(4000000L) /* maxFrameTimeNanos */,
@@ -382,8 +377,7 @@
 
     @Test
     public void testBeginCancel() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -402,13 +396,12 @@
         tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
         verify(tracker).removeObservers();
         // Since the tracker has been cancelled, shouldn't trigger perfetto.
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
     }
 
     @Test
     public void testCancelIfEndVsyncIdEqualsToBeginVsyncId() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -426,13 +419,12 @@
         verify(tracker).removeObservers();
 
         // Should never trigger Perfetto since it is a cancel.
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
     }
 
     @Test
     public void testCancelIfEndVsyncIdLessThanBeginVsyncId() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -450,13 +442,12 @@
         verify(tracker).removeObservers();
 
         // Should never trigger Perfetto since it is a cancel.
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
     }
 
     @Test
     public void testCancelWhenSessionNeverBegun() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
         verify(tracker).removeObservers();
@@ -464,8 +455,7 @@
 
     @Test
     public void testEndWhenSessionNeverBegun() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         tracker.end(FrameTracker.REASON_END_NORMAL);
         verify(tracker).removeObservers();
@@ -473,8 +463,7 @@
 
     @Test
     public void testSurfaceOnlyOtherFrameJanky() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -495,12 +484,12 @@
         sendFrame(tracker, JANK_NONE, 103L);
 
         verify(mSurfaceControlWrapper).removeJankStatsListener(any());
-        verify(tracker).triggerPerfetto();
+        verify(mTrackerListener).triggerPerfetto(any());
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
+                eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
                 eq(2L) /* totalFrames */,
                 eq(1L) /* missedFrames */,
                 eq(0L) /* maxFrameTimeNanos */,
@@ -511,8 +500,7 @@
 
     @Test
     public void testSurfaceOnlyFirstFrameJanky() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -533,12 +521,12 @@
         sendFrame(tracker, JANK_NONE, 103L);
 
         verify(mSurfaceControlWrapper).removeJankStatsListener(any());
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
+                eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
                 eq(0L) /* maxFrameTimeNanos */,
@@ -549,8 +537,7 @@
 
     @Test
     public void testSurfaceOnlyLastFrameJanky() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -571,12 +558,12 @@
         sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 103L);
 
         verify(mSurfaceControlWrapper).removeJankStatsListener(any());
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
+                eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
                 eq(0L) /* maxFrameTimeNanos */,
@@ -587,8 +574,7 @@
 
     @Test
     public void testMaxSuccessiveMissedFramesCount() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
         mRunnableArgumentCaptor.getValue().run();
@@ -604,11 +590,11 @@
         sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 106L);
         sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 107L);
         verify(mSurfaceControlWrapper).removeJankStatsListener(any());
-        verify(tracker).triggerPerfetto();
+        verify(mTrackerListener).triggerPerfetto(any());
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
+                eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
                 eq(6L) /* totalFrames */,
                 eq(5L) /* missedFrames */,
                 eq(0L) /* maxFrameTimeNanos */,
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index b61f995..68095e5 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -20,23 +20,9 @@
 
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_ADD;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_APP_START;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_APPEAR;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_REMOVE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_EXPAND;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE;
-import static com.android.internal.jank.InteractionJankMonitor.MAX_LENGTH_OF_CUJ_NAME;
-import static com.android.internal.jank.InteractionJankMonitor.getNameOfCuj;
+import static com.android.internal.jank.InteractionJankMonitor.Configuration.generateSessionName;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -52,12 +38,11 @@
 import android.os.HandlerThread;
 import android.os.SystemClock;
 import android.provider.DeviceConfig;
-import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewAttachTestActivity;
 
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.filters.SmallTest;
-import androidx.test.rule.ActivityTestRule;
 
 import com.android.internal.jank.FrameTracker.ChoreographerWrapper;
 import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
@@ -66,101 +51,54 @@
 import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
 import com.android.internal.jank.FrameTracker.ViewRootWrapper;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
-import com.android.internal.jank.InteractionJankMonitor.Session;
-import com.android.internal.util.FrameworkStatsLog;
 
 import com.google.common.truth.Expect;
 
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
 
 @SmallTest
 public class InteractionJankMonitorTest {
-    private static final String CUJ_POSTFIX = "";
-    private static final SparseArray<String> ENUM_NAME_EXCEPTION_MAP = new SparseArray<>();
-    private static final String ENUM_NAME_PREFIX =
-            "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__";
-
-    private static final ArrayList<String> DEPRECATED_VALUES = new ArrayList<>();
-
     private ViewAttachTestActivity mActivity;
     private View mView;
+    private Handler mHandler;
     private HandlerThread mWorker;
 
     @Rule
-    public ActivityTestRule<ViewAttachTestActivity> mRule =
-            new ActivityTestRule<>(ViewAttachTestActivity.class);
+    public ActivityScenarioRule<ViewAttachTestActivity> mRule =
+            new ActivityScenarioRule<>(ViewAttachTestActivity.class);
 
     @Rule
     public final Expect mExpect = Expect.create();
 
-    @BeforeClass
-    public static void initialize() {
-        ENUM_NAME_EXCEPTION_MAP.put(CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD"));
-        ENUM_NAME_EXCEPTION_MAP.put(CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, getEnumName("SHADE_HEADS_UP_DISAPPEAR"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, getEnumName("NOTIFICATION_SHADE_SWIPE"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, getEnumName("SHADE_QS_EXPAND_COLLAPSE"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, getEnumName("SHADE_QS_SCROLL_SWIPE"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING"));
-        DEPRECATED_VALUES.add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION");
-    }
-
-    private static String getEnumName(String name) {
-        return formatSimple("%s%s", ENUM_NAME_PREFIX, name);
-    }
-
     @Before
     public void setup() {
-        // Prepare an activity for getting ThreadedRenderer later.
-        mActivity = mRule.getActivity();
+        mRule.getScenario().onActivity(activity -> mActivity = activity);
         mView = mActivity.getWindow().getDecorView();
         assertThat(mView.isAttachedToWindow()).isTrue();
 
-        Handler handler = spy(new Handler(mActivity.getMainLooper()));
-        doReturn(true).when(handler).sendMessageAtTime(any(), anyLong());
+        mHandler = spy(new Handler(mActivity.getMainLooper()));
+        doReturn(true).when(mHandler).sendMessageAtTime(any(), anyLong());
         mWorker = mock(HandlerThread.class);
-        doReturn(handler).when(mWorker).getThreadHandler();
+        doReturn(mHandler).when(mWorker).getThreadHandler();
     }
 
     @Test
     public void testBeginEnd() {
         InteractionJankMonitor monitor = createMockedInteractionJankMonitor();
-        FrameTracker tracker = createMockedFrameTracker(monitor, null);
-        doReturn(tracker).when(monitor).createFrameTracker(any(), any());
+        FrameTracker tracker = createMockedFrameTracker();
+        doReturn(tracker).when(monitor).createFrameTracker(any());
         doNothing().when(tracker).begin();
         doReturn(true).when(tracker).end(anyInt());
 
         // Simulate a trace session and see if begin / end are invoked.
-        assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
+        assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
         verify(tracker).begin();
-        assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
+        assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
         verify(tracker).end(REASON_END_NORMAL);
     }
 
@@ -172,10 +110,10 @@
         propertiesValues.put("enabled", "false");
         DeviceConfig.Properties properties = new DeviceConfig.Properties(
                 DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR, propertiesValues);
-        monitor.getPropertiesChangedListener().onPropertiesChanged(properties);
+        monitor.updateProperties(properties);
 
-        assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
-        assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+        assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+        assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
     }
 
     @Test
@@ -185,145 +123,57 @@
         assertThat(view.isAttachedToWindow()).isFalse();
 
         // Should return false if the view passed in is not attached to window yet.
-        assertThat(monitor.begin(view, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
-        assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+        assertThat(monitor.begin(view, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+        assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
     }
 
     @Test
     public void testBeginTimeout() {
         ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
         InteractionJankMonitor monitor = createMockedInteractionJankMonitor();
-        FrameTracker tracker = createMockedFrameTracker(monitor, null);
-        doReturn(tracker).when(monitor).createFrameTracker(any(), any());
+        FrameTracker tracker = createMockedFrameTracker();
+        doReturn(tracker).when(monitor).createFrameTracker(any());
         doNothing().when(tracker).begin();
         doReturn(true).when(tracker).cancel(anyInt());
 
-        assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
+        assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
         verify(tracker).begin();
-        verify(monitor).scheduleTimeoutAction(anyInt(), anyLong(), captor.capture());
+        verify(monitor).scheduleTimeoutAction(any(), captor.capture());
         Runnable runnable = captor.getValue();
         assertThat(runnable).isNotNull();
-        mWorker.getThreadHandler().removeCallbacks(runnable);
         runnable.run();
-        verify(monitor).cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, REASON_CANCEL_TIMEOUT);
+        verify(monitor).cancel(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, REASON_CANCEL_TIMEOUT);
         verify(tracker).cancel(REASON_CANCEL_TIMEOUT);
     }
 
     @Test
-    public void testCujTypeEnumCorrectlyDefined() throws Exception {
-        List<Field> cujEnumFields =
-                Arrays.stream(InteractionJankMonitor.class.getDeclaredFields())
-                        .filter(field -> field.getName().startsWith("CUJ_")
-                                && Modifier.isStatic(field.getModifiers())
-                                && field.getType() == int.class)
-                        .collect(Collectors.toList());
-
-        HashSet<Integer> allValues = new HashSet<>();
-        for (Field field : cujEnumFields) {
-            int fieldValue = field.getInt(null);
-            assertWithMessage(
-                    "Field %s must have a mapping to a value in CUJ_TO_STATSD_INTERACTION_TYPE",
-                    field.getName())
-                    .that(fieldValue < CUJ_TO_STATSD_INTERACTION_TYPE.length)
-                    .isTrue();
-            assertWithMessage("All CujType values must be unique. Field %s repeats existing value.",
-                    field.getName())
-                    .that(allValues.add(fieldValue))
-                    .isTrue();
-        }
-    }
-
-    @Test
-    public void testCujsMapToEnumsCorrectly() {
-        List<Field> cujs = Arrays.stream(InteractionJankMonitor.class.getDeclaredFields())
-                .filter(f -> f.getName().startsWith("CUJ_")
-                        && Modifier.isStatic(f.getModifiers())
-                        && f.getType() == int.class)
-                .collect(Collectors.toList());
-
-        Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
-                .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
-                        && !DEPRECATED_VALUES.contains(f.getName())
-                        && Modifier.isStatic(f.getModifiers())
-                        && f.getType() == int.class)
-                .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName));
-
-        assertThat(enumsMap.size() - 1).isEqualTo(cujs.size());
-
-        cujs.forEach(f -> {
-            final int cuj = getIntFieldChecked(f);
-            final String cujName = f.getName();
-            final String expectedEnumName = ENUM_NAME_EXCEPTION_MAP.contains(cuj)
-                    ? ENUM_NAME_EXCEPTION_MAP.get(cuj)
-                    : formatSimple("%s%s", ENUM_NAME_PREFIX, cujName.substring(4));
-            final int enumKey = CUJ_TO_STATSD_INTERACTION_TYPE[cuj];
-            final String enumName = enumsMap.get(enumKey);
-            final String expectedNameOfCuj = formatSimple("CUJ_%s", getNameOfCuj(cuj));
-
-            mExpect
-                    .withMessage(formatSimple(
-                            "%s (%d) not matches %s (%d)", cujName, cuj, enumName, enumKey))
-                    .that(expectedEnumName.equals(enumName))
-                    .isTrue();
-            mExpect
-                    .withMessage(
-                            formatSimple("getNameOfCuj(%d) not matches: %s, expected=%s",
-                                    cuj, cujName, expectedNameOfCuj))
-                    .that(cujName.equals(expectedNameOfCuj))
-                    .isTrue();
-        });
-    }
-
-    @Test
-    public void testCujNameLimit() {
-        Arrays.stream(InteractionJankMonitor.class.getDeclaredFields())
-                .filter(f -> f.getName().startsWith("CUJ_")
-                        && Modifier.isStatic(f.getModifiers())
-                        && f.getType() == int.class)
-                .forEach(f -> {
-                    final int cuj = getIntFieldChecked(f);
-                    mExpect
-                            .withMessage(formatSimple(
-                                    "Too long CUJ(%d) name: %s", cuj, getNameOfCuj(cuj)))
-                            .that(getNameOfCuj(cuj).length())
-                            .isAtMost(MAX_LENGTH_OF_CUJ_NAME);
-                });
-    }
-
-    @Test
     public void testSessionNameLengthLimit() {
-        final int cujType = CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
-        final String cujName = getNameOfCuj(cujType);
+        final int cujType = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
+        final String cujName = Cuj.getNameOfCuj(cujType);
         final String cujTag = "ThisIsTheCujTag";
         final String tooLongTag = cujTag.repeat(10);
 
         // Normal case, no postfix.
-        Session noPostfix = new Session(cujType, "");
-        assertThat(noPostfix.getName()).isEqualTo(formatSimple("J<%s>", cujName));
+        assertThat(generateSessionName(cujName, "")).isEqualTo(formatSimple("J<%s>", cujName));
 
         // Normal case, with postfix.
-        Session withPostfix = new Session(cujType, cujTag);
-        assertThat(withPostfix.getName()).isEqualTo(formatSimple("J<%s::%s>", cujName, cujTag));
+        assertThat(generateSessionName(cujName, cujTag))
+                .isEqualTo(formatSimple("J<%s::%s>", cujName, cujTag));
 
         // Since the length of the cuj name is tested in another test, no need to test it here.
         // Too long postfix case, should trim the postfix and keep the cuj name completed.
         final String expectedTrimmedName = formatSimple("J<%s::%s>", cujName,
                 "ThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThi...");
-        Session longPostfix = new Session(cujType, tooLongTag);
-        assertThat(longPostfix.getName()).isEqualTo(expectedTrimmedName);
+        assertThat(generateSessionName(cujName, tooLongTag)).isEqualTo(expectedTrimmedName);
     }
 
     private InteractionJankMonitor createMockedInteractionJankMonitor() {
         InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker));
-        doReturn(true).when(monitor).shouldMonitor(anyInt());
+        doReturn(true).when(monitor).shouldMonitor();
         return monitor;
     }
 
-    private FrameTracker createMockedFrameTracker(InteractionJankMonitor monitor,
-            FrameTracker.FrameTrackerListener listener) {
-        Session session = spy(new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX));
-        doReturn(false).when(session).logToStatsd();
-
+    private FrameTracker createMockedFrameTracker() {
         ThreadedRendererWrapper threadedRenderer = mock(ThreadedRendererWrapper.class);
         doNothing().when(threadedRenderer).addObserver(any());
         doNothing().when(threadedRenderer).removeObserver(any());
@@ -342,27 +192,19 @@
         Configuration configuration = mock(Configuration.class);
         when(configuration.isSurfaceOnly()).thenReturn(false);
         when(configuration.getView()).thenReturn(mView);
-        when(configuration.getHandler()).thenReturn(mView.getHandler());
         when(configuration.getDisplayId()).thenReturn(42);
+        when(configuration.logToStatsd()).thenReturn(false);
+        when(configuration.getHandler()).thenReturn(mHandler);
 
-        FrameTracker tracker = spy(new FrameTracker(monitor, session, mWorker.getThreadHandler(),
+        FrameTracker tracker = spy(new FrameTracker(configuration,
                 threadedRenderer, viewRoot, surfaceControl, choreographer,
                 new FrameMetricsWrapper(), new StatsLogWrapper(null),
                 /* traceThresholdMissedFrames= */ 1,
-                /* traceThresholdFrameTimeMillis= */ -1, listener, configuration));
+                /* traceThresholdFrameTimeMillis= */ -1,
+                /* listener */ null));
 
         doNothing().when(tracker).postTraceStartMarker(any());
-        doNothing().when(tracker).triggerPerfetto();
-        doReturn(configuration.getHandler()).when(tracker).getHandler();
 
         return tracker;
     }
-
-    private int getIntFieldChecked(Field field) {
-        try {
-            return field.getInt(null);
-        } catch (IllegalAccessException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
 }
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 2237ba1..aaddf0e 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -169,6 +169,12 @@
       "group": "WM_DEBUG_WINDOW_ORGANIZER",
       "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
     },
+    "-1961637874": {
+      "message": "DeferredDisplayUpdater: applying DisplayInfo immediately",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java"
+    },
     "-1949279037": {
       "message": "Attempted to add input method window with bad token %s.  Aborting.",
       "level": "WARN",
@@ -313,6 +319,12 @@
       "group": "WM_DEBUG_RESIZE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-1818910559": {
+      "message": "DeferredDisplayUpdater: applied DisplayInfo after deferring",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java"
+    },
     "-1814361639": {
       "message": "Set IME snapshot position: (%d, %d)",
       "level": "INFO",
@@ -595,6 +607,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1518132958": {
+      "message": "fractionRendered boundsOverSource=%f",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "-1517908912": {
       "message": "requestScrollCapture: caught exception dispatching to window.token=%s",
       "level": "WARN",
@@ -961,6 +979,12 @@
       "group": "WM_DEBUG_CONTENT_RECORDING",
       "at": "com\/android\/server\/wm\/ContentRecorder.java"
     },
+    "-1209762265": {
+      "message": "Registering listener=%s with id=%d for window=%s with %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "-1209252064": {
       "message": "Clear animatingExit: reason=clearAnimatingFlags win=%s",
       "level": "DEBUG",
@@ -1333,6 +1357,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "-888703350": {
+      "message": "Skipping %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "-883738232": {
       "message": "Adding more than one toast window for UID at a time.",
       "level": "WARN",
@@ -1909,6 +1939,12 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "-415346336": {
+      "message": "DeferredDisplayUpdater: partially applying DisplayInfo immediately",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java"
+    },
     "-401282500": {
       "message": "destroyIfPossible: r=%s destroy returned removed=%s",
       "level": "DEBUG",
@@ -1957,6 +1993,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "-376950429": {
+      "message": "DeferredDisplayUpdater: deferring DisplayInfo update",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java"
+    },
     "-374767836": {
       "message": "setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s",
       "level": "VERBOSE",
@@ -2803,6 +2845,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "360319850": {
+      "message": "fractionRendered scale=%f",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "364992694": {
       "message": "freezeDisplayRotation: current rotation=%d, new rotation=%d, caller=%s",
       "level": "VERBOSE",
@@ -2983,6 +3031,12 @@
       "group": "WM_DEBUG_BACK_PREVIEW",
       "at": "com\/android\/server\/wm\/BackNavigationController.java"
     },
+    "532771960": {
+      "message": "Adding untrusted state listener=%s with id=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "535103992": {
       "message": "Wallpaper may change!  Adjusting",
       "level": "VERBOSE",
@@ -3061,6 +3115,12 @@
       "group": "WM_DEBUG_DREAM",
       "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
     },
+    "605179032": {
+      "message": "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "608694300": {
       "message": "  NEW SURFACE SESSION %s",
       "level": "INFO",
@@ -3289,6 +3349,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "824532141": {
+      "message": "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f minFractionRendered=%f",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "829434921": {
       "message": "Draw state now committed in %s",
       "level": "VERBOSE",
@@ -3583,6 +3649,12 @@
       "group": "WM_SHOW_SURFACE_ALLOC",
       "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
     },
+    "1090378847": {
+      "message": "Checking %d windows",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "1100065297": {
       "message": "Attempted to get IME policy of a display that does not exist: %d",
       "level": "WARN",
@@ -3715,6 +3787,12 @@
       "group": "WM_DEBUG_FOCUS",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1251721200": {
+      "message": "unregister failed, couldn't find deathRecipient for %s with id=%d",
+      "level": "ERROR",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "1252594551": {
       "message": "Window types in WindowContext and LayoutParams.type should match! Type from LayoutParams is %d, but type from WindowContext is %d",
       "level": "WARN",
@@ -3853,6 +3931,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/TaskDisplayArea.java"
     },
+    "1382634842": {
+      "message": "Unregistering listener=%s with id=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "1393721079": {
       "message": "Starting remote display change: from [rot = %d], to [%dx%d, rot = %d]",
       "level": "VERBOSE",
@@ -3901,6 +3985,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1445704347": {
+      "message": "coveredRegionsAbove updated with %s frame:%s region:%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "1448683958": {
       "message": "Override pending remote transitionSet=%b adapter=%s",
       "level": "INFO",
@@ -4201,6 +4291,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
+    "1786463281": {
+      "message": "Adding trusted state listener=%s with id=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "1789321832": {
       "message": "Then token:%s is invalid. It might be removed",
       "level": "WARN",
@@ -4375,6 +4471,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "1955470028": {
+      "message": "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s scale=%f,%f",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "1964565370": {
       "message": "Starting remote animation",
       "level": "INFO",
@@ -4659,6 +4761,9 @@
     "WM_DEBUG_TASKS": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_TPL": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_WALLPAPER": {
       "tag": "WindowManager"
     },
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index dc413b0..a32b435 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -28,7 +28,7 @@
 import android.window.IBackAnimationRunner;
 import android.window.IOnBackInvokedCallback;
 
-import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.jank.Cuj.CujType;
 import com.android.wm.shell.common.InteractionJankMonitorUtils;
 
 /**
@@ -42,7 +42,7 @@
 
     private final IOnBackInvokedCallback mCallback;
     private final IRemoteAnimationRunner mRunner;
-    private final @InteractionJankMonitor.CujType int mCujType;
+    private final @CujType int mCujType;
     private final Context mContext;
 
     // Whether we are waiting to receive onAnimationStart
@@ -55,7 +55,7 @@
             @NonNull IOnBackInvokedCallback callback,
             @NonNull IRemoteAnimationRunner runner,
             @NonNull Context context,
-            @InteractionJankMonitor.CujType int cujType) {
+            @CujType int cujType) {
         mCallback = callback;
         mRunner = runner;
         mCujType = cujType;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
index ec344d3..86f00b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
@@ -23,6 +23,7 @@
 import android.view.SurfaceControl;
 import android.view.View;
 
+import com.android.internal.jank.Cuj.CujType;
 import com.android.internal.jank.InteractionJankMonitor;
 
 /** Utils class for simplfy InteractionJank trancing call */
@@ -31,11 +32,11 @@
     /**
      * Begin a trace session.
      *
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link CujType}.
      * @param view the view to trace
      * @param tag the tag to distinguish different flow of same type CUJ.
      */
-    public static void beginTracing(@InteractionJankMonitor.CujType int cujType,
+    public static void beginTracing(@CujType int cujType,
             @NonNull View view, @Nullable String tag) {
         final InteractionJankMonitor.Configuration.Builder builder =
                 InteractionJankMonitor.Configuration.Builder.withView(cujType, view);
@@ -48,12 +49,12 @@
     /**
      * Begin a trace session.
      *
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link CujType}.
      * @param context the context
      * @param surface the surface to trace
      * @param tag the tag to distinguish different flow of same type CUJ.
      */
-    public static void beginTracing(@InteractionJankMonitor.CujType int cujType,
+    public static void beginTracing(@CujType int cujType,
             @NonNull Context context, @NonNull SurfaceControl surface, @Nullable String tag) {
         final InteractionJankMonitor.Configuration.Builder builder =
                 InteractionJankMonitor.Configuration.Builder.withSurface(cujType, context, surface);
@@ -66,18 +67,18 @@
     /**
      * End a trace session.
      *
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link CujType}.
      */
-    public static void endTracing(@InteractionJankMonitor.CujType int cujType) {
+    public static void endTracing(@CujType int cujType) {
         InteractionJankMonitor.getInstance().end(cujType);
     }
 
     /**
      * Cancel the trace session.
      *
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link CujType}.
      */
-    public static void cancelTracing(@InteractionJankMonitor.CujType int cujType) {
+    public static void cancelTracing(@CujType int cujType) {
         InteractionJankMonitor.getInstance().cancel(cujType);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 38ce164..d157ca8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -16,8 +16,8 @@
 
 package com.android.wm.shell.onehanded;
 
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_ENTER_TRANSITION;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_EXIT_TRANSITION;
+import static com.android.internal.jank.Cuj.CUJ_ONE_HANDED_ENTER_TRANSITION;
+import static com.android.internal.jank.Cuj.CUJ_ONE_HANDED_EXIT_TRANSITION;
 import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_EXIT;
 import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_TRIGGER;
 
@@ -37,6 +37,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.jank.Cuj.CujType;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
@@ -327,7 +328,7 @@
         mTransitionCallbacks.add(callback);
     }
 
-    void beginCUJTracing(@InteractionJankMonitor.CujType int cujType, @Nullable String tag) {
+    void beginCUJTracing(@CujType int cujType, @Nullable String tag) {
         final Map.Entry<WindowContainerToken, SurfaceControl> firstEntry =
                 getDisplayAreaTokenMap().entrySet().iterator().next();
         final InteractionJankMonitor.Configuration.Builder builder =
@@ -339,11 +340,11 @@
         mJankMonitor.begin(builder);
     }
 
-    void endCUJTracing(@InteractionJankMonitor.CujType int cujType) {
+    void endCUJTracing(@CujType int cujType) {
         mJankMonitor.end(cujType);
     }
 
-    void cancelCUJTracing(@InteractionJankMonitor.CujType int cujType) {
+    void cancelCUJTracing(@CujType int cujType) {
         mJankMonitor.cancel(cujType);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 8eb4a5a..f5c01d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -770,6 +770,7 @@
                     }
                     if (mContentOverlay != null) {
                         mContentOverlay.onAnimationEnd(tx, destBounds);
+                        clearContentOverlay();
                     }
                 }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 850b06a0..a2bd47c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -109,7 +109,7 @@
 
         @Override
         public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
-            // Do nothing. Color overlay should be fully opaque by now.
+            // Do nothing. Color overlay should be fully opaque by now, ready for fade out.
         }
 
         private float[] getContentOverlayColor(Context context) {
@@ -167,7 +167,7 @@
 
         @Override
         public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
-            atomicTx.remove(mLeash);
+            // Do nothing. Snapshot overlay should be fully opaque by now, ready for fade out.
         }
     }
 
@@ -193,19 +193,12 @@
                     MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
             mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
 
-            final int appWidth = appBounds.width();
-            final int appHeight = appBounds.height();
-
-            // In order to have the overlay always cover the pip window during the transition, the
-            // overlay will be drawn with the max size of the start and end bounds in different
-            // rotation.
-            final int overlaySize = Math.max(Math.max(appWidth, appHeight),
-                    Math.max(destinationBounds.width(), destinationBounds.height())) + 1;
+            final int overlaySize = getOverlaySize(appBounds, destinationBounds);
             mOverlayHalfSize = overlaySize >> 1;
 
             // When the activity is in the secondary split, make sure the scaling center is not
             // offset.
-            mAppBounds = new Rect(0, 0, appWidth, appHeight);
+            mAppBounds = new Rect(0, 0, appBounds.width(), appBounds.height());
 
             mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888);
             prepareAppIconOverlay(appIcon);
@@ -215,6 +208,21 @@
                     .build();
         }
 
+        /**
+         * Returns the size of the app icon overlay.
+         *
+         * In order to have the overlay always cover the pip window during the transition,
+         * the overlay will be drawn with the max size of the start and end bounds in different
+         * rotation.
+         */
+        public static int getOverlaySize(Rect appBounds, Rect destinationBounds) {
+            final int appWidth = appBounds.width();
+            final int appHeight = appBounds.height();
+
+            return Math.max(Math.max(appWidth, appHeight),
+                    Math.max(destinationBounds.width(), destinationBounds.height())) + 1;
+        }
+
         @Override
         public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
             tx.show(mLeash);
@@ -248,7 +256,7 @@
 
         @Override
         public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
-            atomicTx.remove(mLeash);
+            // Do nothing. Icon overlay should be fully opaque by now, ready for fade out.
         }
 
         @Override
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 c1164fc..743b1ea 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
@@ -156,74 +156,77 @@
     // These callbacks are called on the update thread
     private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
             new PipAnimationController.PipAnimationCallback() {
-        private boolean mIsCancelled;
-        @Override
-        public void onPipAnimationStart(TaskInfo taskInfo,
-                PipAnimationController.PipTransitionAnimator animator) {
-            final int direction = animator.getTransitionDirection();
-            mIsCancelled = false;
-            sendOnPipTransitionStarted(direction);
-        }
+                private boolean mIsCancelled;
 
-        @Override
-        public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
-                PipAnimationController.PipTransitionAnimator animator) {
-            final int direction = animator.getTransitionDirection();
-            if (mIsCancelled) {
-                sendOnPipTransitionFinished(direction);
-                maybePerformFinishResizeCallback();
-                return;
-            }
-            final int animationType = animator.getAnimationType();
-            final Rect destinationBounds = animator.getDestinationBounds();
-            if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
-                fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
-                        animator::clearContentOverlay, true /* withStartDelay*/);
-            }
-            if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
-                    && direction == TRANSITION_DIRECTION_TO_PIP) {
-                // Notify the display to continue the deferred orientation change.
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                wct.scheduleFinishEnterPip(mToken, destinationBounds);
-                mTaskOrganizer.applyTransaction(wct);
-                // The final task bounds will be applied by onFixedRotationFinished so that all
-                // coordinates are in new rotation.
-                mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
-                mDeferredAnimEndTransaction = tx;
-                return;
-            }
-            final boolean isExitPipDirection = isOutPipDirection(direction)
-                    || isRemovePipDirection(direction);
-            if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP
-                    || isExitPipDirection) {
-                // execute the finish resize callback if needed after the transaction is committed
-                tx.addTransactionCommittedListener(mMainExecutor,
-                        PipTaskOrganizer.this::maybePerformFinishResizeCallback);
+                @Override
+                public void onPipAnimationStart(TaskInfo taskInfo,
+                        PipAnimationController.PipTransitionAnimator animator) {
+                    final int direction = animator.getTransitionDirection();
+                    mIsCancelled = false;
+                    sendOnPipTransitionStarted(direction);
+                }
 
-                // Finish resize as long as we're not exiting PIP, or, if we are, only if this is
-                // the end of an exit PIP animation.
-                // This is necessary in case there was a resize animation ongoing when exit PIP
-                // started, in which case the first resize will be skipped to let the exit
-                // operation handle the final resize out of PIP mode. See b/185306679.
-                finishResizeDelayedIfNeeded(() -> {
-                    finishResize(tx, destinationBounds, direction, animationType);
-                    sendOnPipTransitionFinished(direction);
-                });
-            }
-        }
+                @Override
+                public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
+                        PipAnimationController.PipTransitionAnimator animator) {
+                    final int direction = animator.getTransitionDirection();
+                    if (mIsCancelled) {
+                        sendOnPipTransitionFinished(direction);
+                        maybePerformFinishResizeCallback();
+                        return;
+                    }
+                    final int animationType = animator.getAnimationType();
+                    final Rect destinationBounds = animator.getDestinationBounds();
+                    if (isInPipDirection(direction) && mPipOverlay != null) {
+                        fadeOutAndRemoveOverlay(mPipOverlay,
+                                null /* callback */, true /* withStartDelay*/);
+                    }
+                    if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
+                            && direction == TRANSITION_DIRECTION_TO_PIP) {
+                        // Notify the display to continue the deferred orientation change.
+                        final WindowContainerTransaction wct = new WindowContainerTransaction();
+                        wct.scheduleFinishEnterPip(mToken, destinationBounds);
+                        mTaskOrganizer.applyTransaction(wct);
+                        // The final task bounds will be applied by onFixedRotationFinished so
+                        // that all coordinates are in new rotation.
+                        mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
+                        mDeferredAnimEndTransaction = tx;
+                        return;
+                    }
+                    final boolean isExitPipDirection = isOutPipDirection(direction)
+                            || isRemovePipDirection(direction);
+                    if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP
+                            || isExitPipDirection) {
+                        // execute the finish resize callback if needed after the transaction is
+                        // committed
+                        tx.addTransactionCommittedListener(mMainExecutor,
+                                PipTaskOrganizer.this::maybePerformFinishResizeCallback);
 
-        @Override
-        public void onPipAnimationCancel(TaskInfo taskInfo,
-                PipAnimationController.PipTransitionAnimator animator) {
-            final int direction = animator.getTransitionDirection();
-            mIsCancelled = true;
-            if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
-                fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
-                        animator::clearContentOverlay, true /* withStartDelay */);
-            }
-            sendOnPipTransitionCancelled(direction);
-        }
-    };
+                        // Finish resize as long as we're not exiting PIP, or, if we are, only if
+                        // this is the end of an exit PIP animation.
+                        // This is necessary in case there was a resize animation ongoing when
+                        // exit PIP started, in which case the first resize will be skipped to
+                        // let the exit operation handle the final resize out of PIP mode.
+                        // See b/185306679.
+                        finishResizeDelayedIfNeeded(() -> {
+                            finishResize(tx, destinationBounds, direction, animationType);
+                            sendOnPipTransitionFinished(direction);
+                        });
+                    }
+                }
+
+                @Override
+                public void onPipAnimationCancel(TaskInfo taskInfo,
+                        PipAnimationController.PipTransitionAnimator animator) {
+                    final int direction = animator.getTransitionDirection();
+                    mIsCancelled = true;
+                    if (isInPipDirection(direction) && mPipOverlay != null) {
+                        fadeOutAndRemoveOverlay(mPipOverlay,
+                                null /* callback */, true /* withStartDelay */);
+                    }
+                    sendOnPipTransitionCancelled(direction);
+                }
+            };
 
     /**
      * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
@@ -327,11 +330,18 @@
 
     /**
      * An optional overlay used to mask content changing between an app in/out of PiP, only set if
-     * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true.
+     * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true, only in gesture nav.
      */
     @Nullable
     SurfaceControl mSwipePipToHomeOverlay;
 
+    /**
+     * An optional overlay used to mask content changing between an app in/out of PiP, only set if
+     * {@link PipTransitionState#getInSwipePipToHomeTransition()} is false.
+     */
+    @Nullable
+    SurfaceControl mPipOverlay;
+
     public PipTaskOrganizer(Context context,
             @NonNull SyncTransactionQueue syncTransactionQueue,
             @NonNull PipTransitionState pipTransitionState,
@@ -1766,6 +1776,7 @@
                     animator.setSnapshotContentOverlay(snapshot, sourceHintRect);
                 }
             }
+            mPipOverlay = animator.getContentOverlayLeash();
             // The destination bounds are used for the end rect of animation and the final bounds
             // after animation finishes. So after the animation is started, the destination bounds
             // can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout
@@ -1879,6 +1890,14 @@
     }
 
     private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
+        if (mPipOverlay != null) {
+            if (mPipOverlay != surface) {
+                ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: trying to remove overlay (%s) which is not local reference (%s)",
+                        TAG, surface, mPipOverlay);
+            }
+            mPipOverlay = null;
+        }
         if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
             // Avoid double removal, which is fatal.
             return;
@@ -1907,11 +1926,11 @@
     private void cancelCurrentAnimator() {
         final PipAnimationController.PipTransitionAnimator<?> animator =
                 mPipAnimationController.getCurrentAnimator();
+        // remove any overlays if present
+        if (mPipOverlay != null) {
+            removeContentOverlay(mPipOverlay, null /* callback */);
+        }
         if (animator != null) {
-            if (animator.getContentOverlayLeash() != null) {
-                removeContentOverlay(animator.getContentOverlayLeash(),
-                        animator::clearContentOverlay);
-            }
             PipAnimationController.quietCancel(animator);
             mPipAnimationController.resetAnimatorState();
         }
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 fe4980a..0f3c162 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
@@ -43,6 +43,7 @@
 import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
 
 import android.animation.Animator;
+import android.annotation.IntDef;
 import android.app.ActivityManager;
 import android.app.TaskInfo;
 import android.content.Context;
@@ -76,6 +77,8 @@
 import com.android.wm.shell.util.TransitionUtil;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Optional;
 
 /**
@@ -86,6 +89,18 @@
 
     private static final String TAG = PipTransition.class.getSimpleName();
 
+    /** No fixed rotation, or fixed rotation state is undefined. */
+    private static final int FIXED_ROTATION_UNDEFINED = 0;
+    /**
+     * Fixed rotation detected via callbacks (see PipController#startSwipePipToHome());
+     * this is used in the swipe PiP to home case, since the transitions itself isn't supposed to
+     * see the fixed rotation.
+     */
+    private static final int FIXED_ROTATION_CALLBACK = 1;
+
+    /** Fixed rotation detected in the incoming transition. */
+    private static final int FIXED_ROTATION_TRANSITION = 2;
+
     private final Context mContext;
     private final PipTransitionState mPipTransitionState;
     private final PipDisplayLayoutState mPipDisplayLayoutState;
@@ -106,17 +121,28 @@
     /** The Task window that is currently in PIP windowing mode. */
     @Nullable
     private WindowContainerToken mCurrentPipTaskToken;
-    /** Whether display is in fixed rotation. */
-    private boolean mInFixedRotation;
+
+    @IntDef(prefix = { "FIXED_ROTATION_" }, value =  {
+            FIXED_ROTATION_UNDEFINED,
+            FIXED_ROTATION_CALLBACK,
+            FIXED_ROTATION_TRANSITION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FixedRotationState {}
+
+    /** Fixed rotation state of the display. */
+    private @FixedRotationState int mFixedRotationState = FIXED_ROTATION_UNDEFINED;
     /**
      * The rotation that the display will apply after expanding PiP to fullscreen. This is only
-     * meaningful if {@link #mInFixedRotation} is true.
+     * meaningful if {@link #mFixedRotationState} is {@link #FIXED_ROTATION_TRANSITION}.
      */
     @Surface.Rotation
     private int mEndFixedRotation;
     /** Whether the PIP window has fade out for fixed rotation. */
     private boolean mHasFadeOut;
 
+    private Rect mInitBounds = new Rect();
+
     /** Used for setting transform to a transaction from animator. */
     private final PipAnimationController.PipTransactionHandler mTransactionConsumer =
             new PipAnimationController.PipTransactionHandler() {
@@ -181,8 +207,16 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info);
         final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
-        mInFixedRotation = fixedRotationChange != null;
-        mEndFixedRotation = mInFixedRotation
+        if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
+            // If we are just about to process potential fixed rotation information,
+            // then fixed rotation state should either be UNDEFINED or CALLBACK.
+            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                    "%s: startAnimation() should start with clear fixed rotation state", TAG);
+            mFixedRotationState = FIXED_ROTATION_UNDEFINED;
+        }
+        mFixedRotationState = fixedRotationChange != null
+                ? FIXED_ROTATION_TRANSITION : mFixedRotationState;
+        mEndFixedRotation = mFixedRotationState == FIXED_ROTATION_TRANSITION
                 ? fixedRotationChange.getEndFixedRotation()
                 : ROTATION_UNDEFINED;
 
@@ -347,6 +381,10 @@
     @Override
     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
             @Nullable SurfaceControl.Transaction finishT) {
+        // Transition either finished pre-emptively, got merged, or aborted,
+        // so update fixed rotation state to default.
+        mFixedRotationState = FIXED_ROTATION_UNDEFINED;
+
         if (transition != mExitTransition) {
             return;
         }
@@ -408,7 +446,8 @@
                 // done at the start. But if it is running fixed rotation, there will be a seamless
                 // display transition later. So the last rotation transform needs to be kept to
                 // avoid flickering, and then the display transition will reset the transform.
-                if (!mInFixedRotation && mFinishTransaction != null) {
+                if (mFixedRotationState != FIXED_ROTATION_TRANSITION
+                        && mFinishTransaction != null) {
                     mFinishTransaction.merge(tx);
                 }
             } else {
@@ -426,12 +465,27 @@
                     mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
                             .resetScale(tx, leash, destinationBounds)
                             .round(tx, leash, true /* applyCornerRadius */);
+                    if (mPipOrganizer.mSwipePipToHomeOverlay != null && !mInitBounds.isEmpty()) {
+                        // Resetting the scale for pinned task while re-adjusting its crop,
+                        // also scales the overlay. So we need to update the overlay leash too.
+                        Rect overlayBounds = new Rect(destinationBounds);
+                        final int overlaySize = PipContentOverlay.PipAppIconOverlay
+                                .getOverlaySize(mInitBounds, destinationBounds);
+
+                        overlayBounds.offsetTo(
+                                (destinationBounds.width() - overlaySize) / 2,
+                                (destinationBounds.height() - overlaySize) / 2);
+                        mSurfaceTransactionHelper.resetScale(tx,
+                                mPipOrganizer.mSwipePipToHomeOverlay, overlayBounds);
+                    }
                 }
+                mInitBounds.setEmpty();
                 wct.setBoundsChangeTransaction(taskInfo.token, tx);
             }
             final int displayRotation = taskInfo.getConfiguration().windowConfiguration
                     .getDisplayRotation();
-            if (enteringPip && mInFixedRotation && mEndFixedRotation != displayRotation
+            if (enteringPip && mFixedRotationState == FIXED_ROTATION_TRANSITION
+                    && mEndFixedRotation != displayRotation
                     && hasValidLeash) {
                 // Launcher may update the Shelf height during the animation, which will update the
                 // destination bounds. Because this is in fixed rotation, We need to make sure the
@@ -451,6 +505,8 @@
             mFinishTransaction = null;
             callFinishCallback(wct);
         }
+        // This is the end of transition on the Shell side so update the fixed rotation state.
+        mFixedRotationState = FIXED_ROTATION_UNDEFINED;
         finishResizeForMenu(destinationBounds);
     }
 
@@ -467,6 +523,7 @@
         // mFinishCallback might be null with an outdated mCurrentPipTaskToken
         // for example, when app crashes while in PiP and exit transition has not started
         mCurrentPipTaskToken = null;
+        mFixedRotationState = FIXED_ROTATION_UNDEFINED;
         if (mFinishCallback == null) return;
         mFinishCallback.onTransitionFinished(null /* wct */);
         mFinishCallback = null;
@@ -475,6 +532,9 @@
 
     @Override
     public void onFixedRotationStarted() {
+        if (mFixedRotationState == FIXED_ROTATION_UNDEFINED) {
+            mFixedRotationState = FIXED_ROTATION_CALLBACK;
+        }
         fadeEnteredPipIfNeed(false /* show */);
     }
 
@@ -656,7 +716,7 @@
 
         // Check if it is fixed rotation.
         final int rotationDelta;
-        if (mInFixedRotation) {
+        if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
             final int startRotation = pipChange.getStartRotation();
             final int endRotation = mEndFixedRotation;
             rotationDelta = deltaRotation(startRotation, endRotation);
@@ -873,11 +933,13 @@
         final int startRotation = pipChange.getStartRotation();
         // Check again in case some callers use startEnterAnimation directly so the flag was not
         // set in startAnimation, e.g. from DefaultMixedHandler.
-        if (!mInFixedRotation) {
+        if (mFixedRotationState != FIXED_ROTATION_TRANSITION) {
             mEndFixedRotation = pipChange.getEndFixedRotation();
-            mInFixedRotation = mEndFixedRotation != ROTATION_UNDEFINED;
+            mFixedRotationState = mEndFixedRotation != ROTATION_UNDEFINED
+                    ? FIXED_ROTATION_TRANSITION : mFixedRotationState;
         }
-        final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation();
+        final int endRotation = mFixedRotationState == FIXED_ROTATION_TRANSITION
+                ? mEndFixedRotation : pipChange.getEndRotation();
 
         setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
                 taskInfo.topActivityInfo);
@@ -888,10 +950,15 @@
 
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         final Rect currentBounds = pipChange.getStartAbsBounds();
+
+        // Cache the start bounds for overlay manipulations as a part of finishCallback.
+        mInitBounds.set(currentBounds);
+
         int rotationDelta = deltaRotation(startRotation, endRotation);
         Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
                 taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
-        if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
+        if (rotationDelta != Surface.ROTATION_0
+                && mFixedRotationState == FIXED_ROTATION_TRANSITION) {
             // Need to get the bounds of new rotation in old rotation for fixed rotation,
             computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo,
                     destinationBounds, sourceHintRect);
@@ -955,10 +1022,12 @@
         } else {
             throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
         }
+        mPipOrganizer.mPipOverlay = animator.getContentOverlayLeash();
         animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(mEnterExitAnimationDuration);
-        if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
+        if (rotationDelta != Surface.ROTATION_0
+                && mFixedRotationState == FIXED_ROTATION_TRANSITION) {
             // For fixed rotation, the animation destination bounds is in old rotation coordinates.
             // Set the destination bounds to new coordinates after the animation is finished.
             // ComputeRotatedBounds has changed the DisplayLayout without affecting the animation.
@@ -997,13 +1066,17 @@
             @NonNull SurfaceControl leash, @Nullable Rect sourceHintRect,
             @NonNull Rect destinationBounds,
             @NonNull ActivityManager.RunningTaskInfo pipTaskInfo) {
-        if (mInFixedRotation) {
+        if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
             // If rotation changes when returning to home, the transition should contain both the
             // entering PiP and the display change (PipController#startSwipePipToHome has updated
             // the display layout to new rotation). So it is not expected to see fixed rotation.
             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                     "%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation);
         }
+        Rect appBounds = pipTaskInfo.configuration.windowConfiguration.getAppBounds();
+        if (mFixedRotationState == FIXED_ROTATION_CALLBACK && appBounds != null) {
+            mInitBounds.set(appBounds);
+        }
         final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
         if (swipePipToHomeOverlay != null) {
             // Launcher fade in the overlay on top of the fullscreen Task. It is possible we
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 20c57fa..04911c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -78,9 +78,9 @@
                     if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
                         return;
                     }
-                    if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
-                        mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
-                                animator::clearContentOverlay, true /* withStartDelay*/);
+                    if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) {
+                        mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay,
+                                null /* callback */, true /* withStartDelay*/);
                     }
                     onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx);
                     sendOnPipTransitionFinished(direction);
@@ -90,9 +90,9 @@
                 public void onPipAnimationCancel(TaskInfo taskInfo,
                         PipAnimationController.PipTransitionAnimator animator) {
                     final int direction = animator.getTransitionDirection();
-                    if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
-                        mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
-                                animator::clearContentOverlay, true /* withStartDelay */);
+                    if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) {
+                        mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay,
+                                null /* callback */, true /* withStartDelay */);
                     }
                     sendOnPipTransitionCancelled(animator.getTransitionDirection());
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 56f1c78..7b57097 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -847,9 +847,12 @@
                 .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
                 .orElse(null);
         if (taskInfo != null) {
-            startTask(taskInfo.taskId, position, options);
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
-                    "Start task in background");
+            if (ENABLE_SHELL_TRANSITIONS) {
+                mStageCoordinator.startTask(taskInfo.taskId, position, options);
+            } else {
+                startTask(taskInfo.taskId, position, options);
+            }
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background");
             return;
         }
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index be685b5..449bef5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -263,6 +263,10 @@
             mStartIntent2 = startIntent2;
             mActivatePosition = position;
         }
+        SplitRequest(int taskId1, int position) {
+            mActivateTaskId = taskId1;
+            mActivatePosition = position;
+        }
         SplitRequest(int taskId1, int taskId2, int position) {
             mActivateTaskId = taskId1;
             mActivateTaskId2 = taskId2;
@@ -556,6 +560,27 @@
         }
     }
 
+    /** Use this method to launch an existing Task via a taskId */
+    void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+        mSplitRequest = new SplitRequest(taskId, position);
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+        wct.startTask(taskId, options);
+        // If this should be mixed, send the task to avoid split handle transition directly.
+        if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(taskId, mTaskOrganizer)) {
+            mTaskOrganizer.applyTransaction(wct);
+            return;
+        }
+
+        // If split screen is not activated, we're expecting to open a pair of apps to split.
+        final int extraTransitType = mMainStage.isActive()
+                ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+        prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering);
+
+        mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
+                extraTransitType, !mIsDropEntering);
+    }
+
     /** Launches an activity into split. */
     void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
             @Nullable Bundle options) {
@@ -1593,7 +1618,9 @@
             // Ensure to evict old splitting tasks because the new split pair might be composed by
             // one of the splitting tasks, evicting the task when finishing entering transition
             // won't guarantee to put the task to the indicated new position.
-            mMainStage.evictAllChildren(wct);
+            if (!mIsDropEntering) {
+                mMainStage.evictAllChildren(wct);
+            }
             mMainStage.reparentTopTask(wct);
             prepareSplitLayout(wct, resizeAnim);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index ce7fef2..9f20f49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -48,9 +48,9 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
 import com.android.wm.shell.common.split.SplitScreenUtils;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
 import com.android.wm.shell.pip.PipTransitionController;
@@ -298,7 +298,7 @@
             mixed.mLeftoversHandler = handler.first;
             mActiveTransitions.add(mixed);
             return handler.second;
-        } else if (mUnfoldHandler != null && mUnfoldHandler.hasUnfold(request)) {
+        } else if (mUnfoldHandler != null && mUnfoldHandler.shouldPlayUnfoldAnimation(request)) {
             final WindowContainerTransaction wct =
                     mUnfoldHandler.handleRequest(transition, request);
             if (wct != null) {
@@ -902,6 +902,18 @@
         return false;
     }
 
+    /** Use to when split use taskId to enter, check if this enter transition should be mixed or
+     * not.*/
+    public boolean shouldSplitEnterMixed(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
+        // Check if this intent package is same as pip one or not, if true we want let the pip
+        // task enter split.
+        if (mPipHandler != null) {
+            return mPipHandler.isInPipPackage(
+                    SplitScreenUtils.getPackageName(taskId, shellTaskOrganizer));
+        }
+        return false;
+    }
+
     /** @return whether the transition-request represents a pip-entry. */
     public boolean requestHasPipEnter(TransitionRequestInfo request) {
         return mPipHandler.requestHasPipEnter(request);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 07c5429..20ff79f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -106,7 +106,7 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull TransitionFinishCallback finishCallback) {
-        if (hasUnfold(info) && transition != mTransition) {
+        if (shouldPlayUnfoldAnimation(info) && transition != mTransition) {
             // Take over transition that has unfold, we might receive it if no other handler
             // accepted request in handleRequest, e.g. for rotation + unfold or
             // TRANSIT_NONE + unfold transitions
@@ -213,14 +213,36 @@
     }
 
     /** Whether `request` contains an unfold action. */
-    public boolean hasUnfold(@NonNull TransitionRequestInfo request) {
+    public boolean shouldPlayUnfoldAnimation(@NonNull TransitionRequestInfo request) {
+        // Unfold animation won't play when animations are disabled
+        if (!ValueAnimator.areAnimatorsEnabled()) return false;
+
         return (request.getType() == TRANSIT_CHANGE
                 && request.getDisplayChange() != null
-                && request.getDisplayChange().isPhysicalDisplayChanged());
+                && isUnfoldDisplayChange(request.getDisplayChange()));
+    }
+
+    private boolean isUnfoldDisplayChange(
+            @NonNull TransitionRequestInfo.DisplayChange displayChange) {
+        if (!displayChange.isPhysicalDisplayChanged()) {
+            return false;
+        }
+
+        if (displayChange.getStartAbsBounds() == null || displayChange.getEndAbsBounds() == null) {
+            return false;
+        }
+
+        // Handle only unfolding, currently we don't have an animation when folding
+        final int endArea =
+                displayChange.getEndAbsBounds().width() * displayChange.getEndAbsBounds().height();
+        final int startArea = displayChange.getStartAbsBounds().width()
+                * displayChange.getStartAbsBounds().height();
+
+        return endArea > startArea;
     }
 
     /** Whether `transitionInfo` contains an unfold action. */
-    public boolean hasUnfold(@NonNull TransitionInfo transitionInfo) {
+    public boolean shouldPlayUnfoldAnimation(@NonNull TransitionInfo transitionInfo) {
         // Unfold animation won't play when animations are disabled
         if (!ValueAnimator.areAnimatorsEnabled()) return false;
 
@@ -250,7 +272,7 @@
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
             @NonNull TransitionRequestInfo request) {
-        if (hasUnfold(request)) {
+        if (shouldPlayUnfoldAnimation(request)) {
             mTransition = transition;
             return new WindowContainerTransaction();
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 99cd4f3..855b7ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -235,7 +235,7 @@
         mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
                 SPLIT_POSITION_TOP_OR_LEFT, null);
 
-        verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+        verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
                 isNull());
         verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
         verify(mStageCoordinator, never()).switchSplitPosition(any());
@@ -243,7 +243,6 @@
 
     @Test
     public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
-        doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
         doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
         Intent startIntent = createStartIntent("startActivity");
         PendingIntent pendingIntent =
@@ -260,8 +259,8 @@
 
         mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
                 SPLIT_POSITION_TOP_OR_LEFT, null);
-
-        verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+        verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+        verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
                 isNull());
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index 6d73c12..fc1fe1c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -98,10 +98,13 @@
     }
 
     @Test
-    public void handleRequest_physicalDisplayChange_handlesTransition() {
+    public void handleRequest_physicalDisplayChangeUnfold_handlesTransition() {
         ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
         TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
-                Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true);
+                Display.DEFAULT_DISPLAY)
+                .setPhysicalDisplayChanged(true)
+                .setStartAbsBounds(new Rect(0, 0, 100, 100))
+                .setEndAbsBounds(new Rect(0, 0, 200, 200));
         TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE,
                 triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
 
@@ -112,6 +115,23 @@
     }
 
     @Test
+    public void handleRequest_physicalDisplayChangeFold_doesNotHandleTransition() {
+        ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
+        TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
+                Display.DEFAULT_DISPLAY)
+                .setPhysicalDisplayChanged(true)
+                .setStartAbsBounds(new Rect(0, 0, 200, 200))
+                .setEndAbsBounds(new Rect(0, 0, 100, 100));
+        TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE,
+                triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
+
+        WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition,
+                requestInfo);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
     public void handleRequest_noPhysicalDisplayChange_doesNotHandleTransition() {
         ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
         TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
@@ -306,7 +326,10 @@
     private TransitionRequestInfo createUnfoldTransitionRequestInfo() {
         ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
         TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
-                Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true);
+                Display.DEFAULT_DISPLAY)
+                .setPhysicalDisplayChanged(true)
+                .setStartAbsBounds(new Rect(0, 0, 100, 100))
+                .setEndAbsBounds(new Rect(0, 0, 200, 200));
         return new TransitionRequestInfo(TRANSIT_CHANGE,
                 triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
     }
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 65e1605..bba9c97 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -218,6 +218,15 @@
 
     mLocked.presentation = presentation;
 
+    if (input_flags::enable_pointer_choreographer()) {
+        // When pointer choreographer is enabled, the presentation mode is only set once when the
+        // PointerController is constructed, before the display viewport is provided.
+        // TODO(b/293587049): Clean up the PointerController interface after pointer choreographer
+        // is permanently enabled. The presentation can be set in the constructor.
+        mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER);
+        return;
+    }
+
     if (!mCursorController.isViewportValid()) {
         return;
     }
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 3c0b002..07f63e5 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -62,3 +62,10 @@
     description: "Allows clients of privileged MediaRouter2 that hold INTERACT_ACROSS_USERS_FULL to control routing across users."
     bug: "288580225"
 }
+
+flag {
+    name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name"
+    namespace: "media_solutions"
+    description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos."
+    bug: "314324170"
+}
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 60bf48c..8b136da 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.6.0-beta01"
+    extra["jetpackComposeVersion"] = "1.6.0-beta02"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 90c7d46..f4edb36 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -48,6 +48,7 @@
 import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
 import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
+import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider
 import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
 import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
 import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
@@ -100,6 +101,7 @@
                 SettingsExposedDropdownMenuCheckBoxProvider,
                 SettingsTextFieldPasswordPageProvider,
                 SearchScaffoldPageProvider,
+                SuwScaffoldPageProvider,
                 CardPageProvider,
                 CopyablePageProvider,
             ),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index 1d897f7..6a2e598 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -43,6 +43,7 @@
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
 import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
 import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
+import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider
 import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
 import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
 import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
@@ -59,6 +60,7 @@
             OperateListPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
             SearchScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            SuwScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
new file mode 100644
index 0000000..6fc8de3
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.settingslib.spa.gallery.scaffold
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.SignalCellularAlt
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.gallery.R
+import com.android.settingslib.spa.widget.illustration.Illustration
+import com.android.settingslib.spa.widget.illustration.IllustrationModel
+import com.android.settingslib.spa.widget.illustration.ResourceType
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.BottomAppBarButton
+import com.android.settingslib.spa.widget.scaffold.SuwScaffold
+import com.android.settingslib.spa.widget.ui.SettingsBody
+
+private const val TITLE = "Sample SuwScaffold"
+
+object SuwScaffoldPageProvider : SettingsPageProvider {
+    override val name = "SuwScaffold"
+
+    private val owner = createSettingsPage()
+
+    fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
+        .setUiLayoutFn {
+            Preference(object : PreferenceModel {
+                override val title = TITLE
+                override val onClick = navigator(name)
+            })
+        }
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        Page()
+    }
+}
+
+@Composable
+private fun Page() {
+    SuwScaffold(
+        imageVector = Icons.Outlined.SignalCellularAlt,
+        title = "Connect to mobile network",
+        actionButton = BottomAppBarButton("Next") {},
+        dismissButton = BottomAppBarButton("Cancel") {},
+    ) {
+        Column(Modifier.padding(SettingsDimension.itemPadding)) {
+            SettingsBody("To add another SIM, download a new eSIM.")
+        }
+        Illustration(object : IllustrationModel {
+            override val resId = R.drawable.accessibility_captioning_banner
+            override val resourceType = ResourceType.IMAGE
+        })
+        Column(Modifier.padding(SettingsDimension.itemPadding)) {
+            SettingsBody("To add another SIM, download a new eSIM.")
+        }
+        Illustration(object : IllustrationModel {
+            override val resId = R.drawable.accessibility_captioning_banner
+            override val resourceType = ResourceType.IMAGE
+        })
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index acd90f3..7eccfe5 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,7 +57,7 @@
     api("androidx.slice:slice-builders:1.1.0-alpha02")
     api("androidx.slice:slice-core:1.1.0-alpha02")
     api("androidx.slice:slice-view:1.1.0-alpha02")
-    api("androidx.compose.material3:material3:1.2.0-alpha11")
+    api("androidx.compose.material3:material3:1.2.0-alpha12")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 5a1120e..c143390 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -37,11 +37,13 @@
     val itemPaddingAround = 8.dp
     val itemDividerHeight = 32.dp
 
+    val iconLarge = 48.dp
+
     /** The size when app icon is displayed in list. */
     val appIconItemSize = 32.dp
 
     /** The size when app icon is displayed in App info page. */
-    val appIconInfoSize = 48.dp
+    val appIconInfoSize = iconLarge
 
     /** The vertical padding for buttons. */
     val buttonPaddingVertical = 12.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
index 3819a10..e0dd4e1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
@@ -34,6 +34,7 @@
 fun SettingsOutlinedTextField(
     value: String,
     label: String,
+    errorMessage: String? = null,
     singleLine: Boolean = true,
     enabled: Boolean = true,
     onTextChange: (String) -> Unit,
@@ -49,6 +50,12 @@
         },
         singleLine = singleLine,
         enabled = enabled,
+        isError = errorMessage != null,
+        supportingText = {
+            if (errorMessage != null) {
+                Text(text = errorMessage)
+            }
+        }
     )
 }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
new file mode 100644
index 0000000..354b95d
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.toMediumWeight
+
+data class BottomAppBarButton(
+    val text: String,
+    val onClick: () -> Unit,
+)
+
+@Composable
+fun SuwScaffold(
+    imageVector: ImageVector,
+    title: String,
+    actionButton: BottomAppBarButton? = null,
+    dismissButton: BottomAppBarButton? = null,
+    content: @Composable ColumnScope.() -> Unit,
+) {
+    ActivityTitle(title)
+    Scaffold { innerPadding ->
+        BoxWithConstraints(
+            Modifier
+                .padding(innerPadding)
+                .padding(top = SettingsDimension.itemPaddingAround)
+        ) {
+            // Use single column layout in portrait, two columns in landscape.
+            val useSingleColumn = maxWidth < maxHeight
+            if (useSingleColumn) {
+                Column {
+                    Column(
+                        Modifier
+                            .weight(1f)
+                            .verticalScroll(rememberScrollState())
+                    ) {
+                        Header(imageVector, title)
+                        content()
+                    }
+                    BottomBar(actionButton, dismissButton)
+                }
+            } else {
+                Column(Modifier.padding(horizontal = SettingsDimension.itemPaddingAround)) {
+                    Row((Modifier.weight(1f))) {
+                        Box(Modifier.weight(1f)) {
+                            Header(imageVector, title)
+                        }
+                        Column(
+                            Modifier
+                                .weight(1f)
+                                .verticalScroll(rememberScrollState())) {
+                            content()
+                        }
+                    }
+                    BottomBar(actionButton, dismissButton)
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun Header(
+    imageVector: ImageVector,
+    title: String
+) {
+    Column(Modifier.padding(SettingsDimension.itemPadding)) {
+        Icon(
+            imageVector = imageVector,
+            contentDescription = null,
+            modifier = Modifier
+                .padding(vertical = SettingsDimension.itemPaddingAround)
+                .size(SettingsDimension.iconLarge),
+            tint = MaterialTheme.colorScheme.primary,
+        )
+        Text(
+            text = title,
+            modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingVertical),
+            color = MaterialTheme.colorScheme.onSurface,
+            style = MaterialTheme.typography.displaySmall,
+        )
+    }
+}
+
+@Composable
+private fun BottomBar(
+    actionButton: BottomAppBarButton?,
+    dismissButton: BottomAppBarButton?,
+) {
+    Row(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) {
+        dismissButton?.apply {
+            TextButton(onClick) {
+                ActionText(text)
+            }
+        }
+        Spacer(modifier = Modifier.weight(1f))
+        actionButton?.apply {
+            Button(onClick) {
+                ActionText(text)
+            }
+        }
+    }
+}
+
+@Composable
+private fun ActionText(text: String) {
+    Text(
+        text = text,
+        style = MaterialTheme.typography.bodyMedium.toMediumWeight(),
+    )
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt
new file mode 100644
index 0000000..35c9f78
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.SignalCellularAlt
+import androidx.compose.material3.Text
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SuwScaffoldTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun suwScaffold_titleIsDisplayed() {
+        composeTestRule.setContent {
+            SuwScaffold(imageVector = Icons.Outlined.SignalCellularAlt, title = TITLE) {
+                Text(text = "AAA")
+                Text(text = "BBB")
+            }
+        }
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun suwScaffold_itemsAreDisplayed() {
+        composeTestRule.setContent {
+            SuwScaffold(imageVector = Icons.Outlined.SignalCellularAlt, title = TITLE) {
+                Text(text = "AAA")
+                Text(text = "BBB")
+            }
+        }
+
+        composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
+        composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
+    }
+
+    @Test
+    fun suwScaffold_actionButtonDisplayed() {
+        composeTestRule.setContent {
+            SuwScaffold(
+                imageVector = Icons.Outlined.SignalCellularAlt,
+                title = TITLE,
+                actionButton = BottomAppBarButton(TEXT) {},
+            ) {}
+        }
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+    }
+
+    @Test
+    fun suwScaffold_dismissButtonDisplayed() {
+        composeTestRule.setContent {
+            SuwScaffold(
+                imageVector = Icons.Outlined.SignalCellularAlt,
+                title = TITLE,
+                dismissButton = BottomAppBarButton(TEXT) {},
+            ) {}
+        }
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val TEXT = "Text"
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
index 25833b3..5aee8cd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.core.instrumentation;
 
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_SETTINGS_TOGGLE;
+import static com.android.internal.jank.Cuj.CUJ_SETTINGS_TOGGLE;
 import static com.android.settingslib.core.instrumentation.SettingsJankMonitor.MONITORED_ANIMATION_DURATION_MS;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -34,8 +34,8 @@
 import androidx.preference.SwitchPreference;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.internal.jank.Cuj.CujType;
 import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.jank.InteractionJankMonitor.CujType;
 import com.android.settingslib.testutils.OverpoweredReflectionHelper;
 import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor;
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 7cec99d..8f459c6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -95,7 +95,9 @@
 
     static final int SETTINGS_VERSION_NEW_ENCODING = 121;
 
+    // LINT.IfChange
     public static final int MAX_LENGTH_PER_STRING = 32768;
+    // LINT.ThenChange(/services/core/java/com/android/server/audio/AudioDeviceInventory.java:settings_max_length_per_string)
     private static final long WRITE_SETTINGS_DELAY_MILLIS = 200;
     private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000;
 
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 187d073..168039e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -33,8 +33,8 @@
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
 import com.android.app.animation.Interpolators
+import com.android.internal.jank.Cuj.CujType
 import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.jank.InteractionJankMonitor.CujType
 import com.android.systemui.util.maybeForceFullscreen
 import com.android.systemui.util.registerAnimationOnBackInvoked
 import kotlin.math.roundToInt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index af35ea4..b738e2b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -33,6 +33,7 @@
 import android.view.ViewGroup
 import android.view.ViewGroupOverlay
 import android.widget.FrameLayout
+import com.android.internal.jank.Cuj.CujType
 import com.android.internal.jank.InteractionJankMonitor
 import java.util.LinkedList
 import kotlin.math.min
@@ -58,7 +59,7 @@
     /** The view that will be ghosted and from which the background will be extracted. */
     private val ghostedView: View,
 
-    /** The [InteractionJankMonitor.CujType] associated to this animation. */
+    /** The [CujType] associated to this animation. */
     private val cujType: Int? = null,
     private var interactionJankMonitor: InteractionJankMonitor =
         InteractionJankMonitor.getInstance(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index 1a653c3..47f5663 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalFoundationApi::class)
+
 package com.android.systemui.bouncer.ui.composable
 
 import android.app.AlertDialog
@@ -29,18 +31,18 @@
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.imePadding
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.widthIn
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.KeyboardArrowDown
@@ -51,6 +53,7 @@
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
+import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
@@ -78,6 +81,7 @@
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.transitions
 import com.android.compose.modifiers.thenIf
+import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
 import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
 import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
@@ -100,37 +104,41 @@
     dialogFactory: BouncerDialogFactory,
     modifier: Modifier = Modifier,
 ) {
-    val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible
     val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState()
     val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported)
 
-    when (layout) {
-        BouncerSceneLayout.STANDARD ->
-            StandardLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                modifier = modifier,
-            )
-        BouncerSceneLayout.SIDE_BY_SIDE ->
-            SideBySideLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
-                modifier = modifier,
-            )
-        BouncerSceneLayout.STACKED ->
-            StackedLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
-                modifier = modifier,
-            )
-        BouncerSceneLayout.SPLIT ->
-            SplitLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                modifier = modifier,
-            )
+    Box(
+        // Allows the content within each of the layouts to react to the appearance and
+        // disappearance of the IME, which is also known as the software keyboard.
+        //
+        // Despite the keyboard only being part of the password bouncer, adding it at this level is
+        // both necessary to properly handle the keyboard in all layouts and harmless in cases when
+        // the keyboard isn't used (like the PIN or pattern auth methods).
+        modifier = modifier.imePadding(),
+    ) {
+        when (layout) {
+            BouncerSceneLayout.STANDARD_BOUNCER ->
+                StandardLayout(
+                    viewModel = viewModel,
+                )
+            BouncerSceneLayout.BESIDE_USER_SWITCHER ->
+                BesideUserSwitcherLayout(
+                    viewModel = viewModel,
+                )
+            BouncerSceneLayout.BELOW_USER_SWITCHER ->
+                BelowUserSwitcherLayout(
+                    viewModel = viewModel,
+                )
+            BouncerSceneLayout.SPLIT_BOUNCER ->
+                SplitLayout(
+                    viewModel = viewModel,
+                )
+        }
+
+        Dialog(
+            viewModel = viewModel,
+            dialogFactory = dialogFactory,
+        )
     }
 }
 
@@ -141,15 +149,333 @@
 @Composable
 private fun StandardLayout(
     viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
     modifier: Modifier = Modifier,
-    layout: BouncerSceneLayout = BouncerSceneLayout.STANDARD,
-    outputOnly: Boolean = false,
+) {
+    val isHeightExpanded =
+        LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
+
+    FoldAware(
+        modifier =
+            modifier.padding(
+                top = 92.dp,
+                bottom = 48.dp,
+            ),
+        viewModel = viewModel,
+        aboveFold = {
+            Column(
+                horizontalAlignment = Alignment.CenterHorizontally,
+                modifier = Modifier.fillMaxWidth(),
+            ) {
+                StatusMessage(
+                    viewModel = viewModel,
+                    modifier = Modifier,
+                )
+
+                OutputArea(
+                    viewModel = viewModel,
+                    modifier = Modifier.padding(top = if (isHeightExpanded) 96.dp else 64.dp),
+                )
+            }
+        },
+        belowFold = {
+            Column(
+                horizontalAlignment = Alignment.CenterHorizontally,
+                modifier = Modifier.fillMaxWidth(),
+            ) {
+                Box(
+                    modifier = Modifier.weight(1f),
+                ) {
+                    InputArea(
+                        viewModel = viewModel,
+                        pinButtonRowVerticalSpacing = 12.dp,
+                        centerPatternDotsVertically = false,
+                        modifier = Modifier.align(Alignment.BottomCenter),
+                    )
+                }
+
+                ActionArea(
+                    viewModel = viewModel,
+                    modifier = Modifier.padding(top = 48.dp),
+                )
+            }
+        },
+    )
+}
+
+/**
+ * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable
+ * by double-tapping on the side.
+ */
+@Composable
+private fun SplitLayout(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val authMethod by viewModel.authMethodViewModel.collectAsState()
+
+    Row(
+        modifier =
+            modifier
+                .fillMaxHeight()
+                .padding(
+                    horizontal = 24.dp,
+                    vertical = if (authMethod is PasswordBouncerViewModel) 24.dp else 48.dp,
+                ),
+    ) {
+        // Left side (in left-to-right locales).
+        Box(
+            modifier = Modifier.fillMaxHeight().weight(1f),
+        ) {
+            when (authMethod) {
+                is PinBouncerViewModel -> {
+                    StatusMessage(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.TopCenter),
+                    )
+
+                    OutputArea(viewModel = viewModel, modifier = Modifier.align(Alignment.Center))
+
+                    ActionArea(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.BottomCenter).padding(top = 48.dp),
+                    )
+                }
+                is PatternBouncerViewModel -> {
+                    StatusMessage(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.TopCenter),
+                    )
+
+                    ActionArea(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.BottomCenter).padding(vertical = 48.dp),
+                    )
+                }
+                is PasswordBouncerViewModel -> {
+                    ActionArea(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.BottomCenter),
+                    )
+                }
+                else -> Unit
+            }
+        }
+
+        // Right side (in right-to-left locales).
+        Box(
+            modifier = Modifier.fillMaxHeight().weight(1f),
+        ) {
+            when (authMethod) {
+                is PinBouncerViewModel,
+                is PatternBouncerViewModel -> {
+                    InputArea(
+                        viewModel = viewModel,
+                        pinButtonRowVerticalSpacing = 8.dp,
+                        centerPatternDotsVertically = true,
+                        modifier = Modifier.align(Alignment.Center),
+                    )
+                }
+                is PasswordBouncerViewModel -> {
+                    Column(
+                        horizontalAlignment = Alignment.CenterHorizontally,
+                        modifier = Modifier.fillMaxWidth().align(Alignment.Center),
+                    ) {
+                        StatusMessage(
+                            viewModel = viewModel,
+                        )
+
+                        OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
+                    }
+                }
+                else -> Unit
+            }
+        }
+    }
+}
+
+/**
+ * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap
+ * anywhere on the background to flip their positions.
+ */
+@Composable
+private fun BesideUserSwitcherLayout(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val layoutDirection = LocalLayoutDirection.current
+    val isLeftToRight = layoutDirection == LayoutDirection.Ltr
+    val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) }
+    val isHeightExpanded =
+        LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
+    val authMethod by viewModel.authMethodViewModel.collectAsState()
+
+    Row(
+        modifier =
+            modifier.pointerInput(Unit) {
+                detectTapGestures(
+                    onDoubleTap = { offset ->
+                        // Depending on where the user double tapped, switch the elements such that
+                        // the endContent is closer to the side that was double tapped.
+                        setSwapped(offset.x < size.width / 2)
+                    }
+                )
+            },
+    ) {
+        val animatedOffset by
+            animateFloatAsState(
+                targetValue =
+                    if (!isSwapped) {
+                        // A non-swapped element has its natural placement so it's not offset.
+                        0f
+                    } else if (isLeftToRight) {
+                        // A swapped element has its elements offset horizontally. In the case of
+                        // LTR locales, this means pushing the element to the right, hence the
+                        // positive number.
+                        1f
+                    } else {
+                        // A swapped element has its elements offset horizontally. In the case of
+                        // RTL locales, this means pushing the element to the left, hence the
+                        // negative number.
+                        -1f
+                    },
+                label = "offset",
+            )
+
+        fun Modifier.swappable(inversed: Boolean = false): Modifier {
+            return graphicsLayer {
+                translationX =
+                    size.width *
+                        animatedOffset *
+                        if (inversed) {
+                            // A negative sign is used to make sure this is offset in the direction
+                            // that's opposite to the direction that the user switcher is pushed in.
+                            -1
+                        } else {
+                            1
+                        }
+                alpha = animatedAlpha(animatedOffset)
+            }
+        }
+
+        UserSwitcher(
+            viewModel = viewModel,
+            modifier = Modifier.weight(1f).align(Alignment.CenterVertically).swappable(),
+        )
+
+        FoldAware(
+            modifier =
+                Modifier.weight(1f)
+                    .padding(
+                        if (isHeightExpanded) {
+                            PaddingValues(vertical = 128.dp)
+                        } else {
+                            PaddingValues(top = 94.dp, bottom = 48.dp)
+                        }
+                    )
+                    .swappable(inversed = true),
+            viewModel = viewModel,
+            aboveFold = {
+                Column(
+                    horizontalAlignment = Alignment.CenterHorizontally,
+                    modifier = Modifier.fillMaxWidth()
+                ) {
+                    StatusMessage(
+                        viewModel = viewModel,
+                    )
+
+                    OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
+                }
+            },
+            belowFold = {
+                Column(
+                    horizontalAlignment = Alignment.CenterHorizontally,
+                    modifier = Modifier.fillMaxWidth(),
+                ) {
+                    val isOutputAreaVisible = authMethod !is PatternBouncerViewModel
+                    // If there is an output area and the window is not tall enough, spacing needs
+                    // to be added between the input and the output areas (otherwise the two get
+                    // very squished together).
+                    val addSpacingBetweenOutputAndInput = isOutputAreaVisible && !isHeightExpanded
+
+                    Box(
+                        modifier =
+                            Modifier.weight(1f)
+                                .padding(top = (if (addSpacingBetweenOutputAndInput) 24 else 0).dp),
+                    ) {
+                        InputArea(
+                            viewModel = viewModel,
+                            pinButtonRowVerticalSpacing = 12.dp,
+                            centerPatternDotsVertically = true,
+                            modifier = Modifier.align(Alignment.BottomCenter),
+                        )
+                    }
+
+                    ActionArea(
+                        viewModel = viewModel,
+                        modifier = Modifier.padding(top = 48.dp),
+                    )
+                }
+            },
+        )
+    }
+}
+
+/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */
+@Composable
+private fun BelowUserSwitcherLayout(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    Column(
+        modifier =
+            modifier.padding(
+                vertical = 128.dp,
+            )
+    ) {
+        UserSwitcher(
+            viewModel = viewModel,
+            modifier = Modifier.fillMaxWidth(),
+        )
+
+        Spacer(Modifier.weight(1f))
+
+        Box(modifier = Modifier.fillMaxWidth()) {
+            Column(
+                horizontalAlignment = Alignment.CenterHorizontally,
+                modifier = Modifier.fillMaxWidth(),
+            ) {
+                StatusMessage(
+                    viewModel = viewModel,
+                )
+
+                OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
+
+                InputArea(
+                    viewModel = viewModel,
+                    pinButtonRowVerticalSpacing = 12.dp,
+                    centerPatternDotsVertically = true,
+                    modifier = Modifier.padding(top = 128.dp),
+                )
+
+                ActionArea(
+                    viewModel = viewModel,
+                    modifier = Modifier.padding(top = 48.dp),
+                )
+            }
+        }
+    }
+}
+
+@Composable
+private fun FoldAware(
+    viewModel: BouncerViewModel,
+    aboveFold: @Composable BoxScope.() -> Unit,
+    belowFold: @Composable BoxScope.() -> Unit,
+    modifier: Modifier = Modifier,
 ) {
     val foldPosture: FoldPosture by foldPosture()
     val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState()
-    val isSplitAroundTheFold =
-        foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired
+    val isSplitAroundTheFold = foldPosture == FoldPosture.Tabletop && isSplitAroundTheFoldRequired
     val currentSceneKey =
         if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
 
@@ -160,115 +486,57 @@
         modifier = modifier,
     ) {
         scene(SceneKeys.ContiguousSceneKey) {
-            FoldSplittable(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                layout = layout,
-                outputOnly = outputOnly,
+            FoldableScene(
+                aboveFold = aboveFold,
+                belowFold = belowFold,
                 isSplit = false,
             )
         }
 
         scene(SceneKeys.SplitSceneKey) {
-            FoldSplittable(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                layout = layout,
-                outputOnly = outputOnly,
+            FoldableScene(
+                aboveFold = aboveFold,
+                belowFold = belowFold,
                 isSplit = true,
             )
         }
     }
 }
 
-/**
- * Renders the "standard" layout of the bouncer, where the bouncer is rendered on its own (no user
- * switcher UI) and laid out vertically, centered horizontally.
- *
- * If [isSplit] is `true`, the top and bottom parts of the bouncer are split such that they don't
- * render across the location of the fold hardware when the device is fully or part-way unfolded
- * with the fold hinge in a horizontal position.
- *
- * If [outputOnly] is `true`, only the "output" part of the UI is shown (where the entered PIN
- * "shapes" appear), if `false`, the entire UI is shown, including the area where the user can enter
- * their PIN or pattern.
- */
 @Composable
-private fun SceneScope.FoldSplittable(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
-    layout: BouncerSceneLayout,
-    outputOnly: Boolean,
+private fun SceneScope.FoldableScene(
+    aboveFold: @Composable BoxScope.() -> Unit,
+    belowFold: @Composable BoxScope.() -> Unit,
     isSplit: Boolean,
     modifier: Modifier = Modifier,
 ) {
-    val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
-    val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
-    var dialog: Dialog? by remember { mutableStateOf(null) }
-    val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
     val splitRatio =
         LocalContext.current.resources.getFloat(
             R.dimen.motion_layout_half_fold_bouncer_height_ratio
         )
 
-    Column(modifier = modifier.padding(horizontal = 32.dp)) {
+    Column(
+        modifier = modifier.fillMaxHeight(),
+    ) {
         // Content above the fold, when split on a foldable device in a "table top" posture:
         Box(
             modifier =
                 Modifier.element(SceneElements.AboveFold)
-                    .fillMaxWidth()
                     .then(
                         if (isSplit) {
                             Modifier.weight(splitRatio)
-                        } else if (outputOnly) {
-                            Modifier.fillMaxHeight()
                         } else {
                             Modifier
                         }
                     ),
         ) {
-            Column(
-                horizontalAlignment = Alignment.CenterHorizontally,
-                modifier = Modifier.fillMaxWidth().padding(top = layout.topPadding),
-            ) {
-                Crossfade(
-                    targetState = message,
-                    label = "Bouncer message",
-                    animationSpec = if (message.isUpdateAnimated) tween() else snap(),
-                ) { message ->
-                    Text(
-                        text = message.text,
-                        color = MaterialTheme.colorScheme.onSurface,
-                        style = MaterialTheme.typography.bodyLarge,
-                    )
-                }
-
-                if (!outputOnly) {
-                    Spacer(Modifier.height(layout.spacingBetweenMessageAndEnteredInput))
-
-                    UserInputArea(
-                        viewModel = viewModel,
-                        visibility = UserInputAreaVisibility.OUTPUT_ONLY,
-                        layout = layout,
-                    )
-                }
-            }
-
-            if (outputOnly) {
-                UserInputArea(
-                    viewModel = viewModel,
-                    visibility = UserInputAreaVisibility.OUTPUT_ONLY,
-                    layout = layout,
-                    modifier = Modifier.align(Alignment.Center),
-                )
-            }
+            aboveFold()
         }
 
         // Content below the fold, when split on a foldable device in a "table top" posture:
         Box(
             modifier =
                 Modifier.element(SceneElements.BelowFold)
-                    .fillMaxWidth()
                     .weight(
                         if (isSplit) {
                             1 - splitRatio
@@ -277,73 +545,40 @@
                         }
                     ),
         ) {
-            Column(
-                horizontalAlignment = Alignment.CenterHorizontally,
-                modifier = Modifier.fillMaxSize()
-            ) {
-                if (!outputOnly) {
-                    Box(Modifier.weight(1f)) {
-                        UserInputArea(
-                            viewModel = viewModel,
-                            visibility = UserInputAreaVisibility.INPUT_ONLY,
-                            layout = layout,
-                            modifier = Modifier.align(Alignment.BottomCenter),
-                        )
-                    }
-                }
-
-                Spacer(Modifier.height(48.dp))
-
-                val actionButtonModifier = Modifier.height(56.dp)
-
-                actionButton.let { actionButtonViewModel ->
-                    if (actionButtonViewModel != null) {
-                        BouncerActionButton(
-                            viewModel = actionButtonViewModel,
-                            modifier = actionButtonModifier,
-                        )
-                    } else {
-                        Spacer(modifier = actionButtonModifier)
-                    }
-                }
-
-                Spacer(Modifier.height(layout.bottomPadding))
-            }
-        }
-
-        if (dialogMessage != null) {
-            if (dialog == null) {
-                dialog =
-                    dialogFactory().apply {
-                        setMessage(dialogMessage)
-                        setButton(
-                            DialogInterface.BUTTON_NEUTRAL,
-                            context.getString(R.string.ok),
-                        ) { _, _ ->
-                            viewModel.onThrottlingDialogDismissed()
-                        }
-                        setCancelable(false)
-                        setCanceledOnTouchOutside(false)
-                        show()
-                    }
-            }
-        } else {
-            dialog?.dismiss()
-            dialog = null
+            belowFold()
         }
     }
 }
 
+@Composable
+private fun StatusMessage(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
+
+    Crossfade(
+        targetState = message,
+        label = "Bouncer message",
+        animationSpec = if (message.isUpdateAnimated) tween() else snap(),
+        modifier = modifier,
+    ) {
+        Text(
+            text = it.text,
+            color = MaterialTheme.colorScheme.onSurface,
+            style = MaterialTheme.typography.bodyLarge,
+        )
+    }
+}
+
 /**
- * Renders the user input area, where the user interacts with the UI to enter their credentials.
+ * Renders the user output area, where the user sees what they entered.
  *
- * For example, this can be the pattern input area, the password text box, or pin pad.
+ * For example, this can be the PIN shapes or password text field.
  */
 @Composable
-private fun UserInputArea(
+private fun OutputArea(
     viewModel: BouncerViewModel,
-    visibility: UserInputAreaVisibility,
-    layout: BouncerSceneLayout,
     modifier: Modifier = Modifier,
 ) {
     val authMethodViewModel: AuthMethodBouncerViewModel? by
@@ -351,66 +586,115 @@
 
     when (val nonNullViewModel = authMethodViewModel) {
         is PinBouncerViewModel ->
-            when (visibility) {
-                UserInputAreaVisibility.OUTPUT_ONLY ->
-                    PinInputDisplay(
-                        viewModel = nonNullViewModel,
-                        modifier = modifier,
-                    )
-                UserInputAreaVisibility.INPUT_ONLY ->
-                    PinPad(
-                        viewModel = nonNullViewModel,
-                        layout = layout,
-                        modifier = modifier,
-                    )
-            }
+            PinInputDisplay(
+                viewModel = nonNullViewModel,
+                modifier = modifier,
+            )
         is PasswordBouncerViewModel ->
-            if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
-                PasswordBouncer(
-                    viewModel = nonNullViewModel,
-                    modifier = modifier,
-                )
-            }
-        is PatternBouncerViewModel ->
-            if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
-                PatternBouncer(
-                    viewModel = nonNullViewModel,
-                    layout = layout,
-                    modifier = modifier.aspectRatio(1f, matchHeightConstraintsFirst = false),
-                )
-            }
+            PasswordBouncer(
+                viewModel = nonNullViewModel,
+                modifier = modifier,
+            )
         else -> Unit
     }
 }
 
 /**
- * Renders the action button on the bouncer, which triggers either Return to Call or Emergency Call.
+ * Renders the user input area, where the user enters their credentials.
+ *
+ * For example, this can be the pattern input area or the PIN pad.
  */
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun BouncerActionButton(
-    viewModel: BouncerActionButtonModel,
+private fun InputArea(
+    viewModel: BouncerViewModel,
+    pinButtonRowVerticalSpacing: Dp,
+    centerPatternDotsVertically: Boolean,
     modifier: Modifier = Modifier,
 ) {
-    Button(
-        onClick = viewModel.onClick,
-        modifier =
-            modifier.thenIf(viewModel.onLongClick != null) {
-                Modifier.combinedClickable(
-                    onClick = viewModel.onClick,
-                    onLongClick = viewModel.onLongClick,
+    val authMethodViewModel: AuthMethodBouncerViewModel? by
+        viewModel.authMethodViewModel.collectAsState()
+
+    when (val nonNullViewModel = authMethodViewModel) {
+        is PinBouncerViewModel -> {
+            PinPad(
+                viewModel = nonNullViewModel,
+                verticalSpacing = pinButtonRowVerticalSpacing,
+                modifier = modifier,
+            )
+        }
+        is PatternBouncerViewModel -> {
+            PatternBouncer(
+                viewModel = nonNullViewModel,
+                centerDotsVertically = centerPatternDotsVertically,
+                modifier = modifier,
+            )
+        }
+        else -> Unit
+    }
+}
+
+@Composable
+private fun ActionArea(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
+
+    actionButton?.let { actionButtonViewModel ->
+        Box(
+            modifier = modifier,
+        ) {
+            Button(
+                onClick = actionButtonViewModel.onClick,
+                modifier =
+                    Modifier.height(56.dp).thenIf(actionButtonViewModel.onLongClick != null) {
+                        Modifier.combinedClickable(
+                            onClick = actionButtonViewModel.onClick,
+                            onLongClick = actionButtonViewModel.onLongClick,
+                        )
+                    },
+                colors =
+                    ButtonDefaults.buttonColors(
+                        containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                        contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+                    ),
+            ) {
+                Text(
+                    text = actionButtonViewModel.label,
+                    style = MaterialTheme.typography.bodyMedium,
                 )
-            },
-        colors =
-            ButtonDefaults.buttonColors(
-                containerColor = MaterialTheme.colorScheme.tertiaryContainer,
-                contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
-            ),
-    ) {
-        Text(
-            text = viewModel.label,
-            style = MaterialTheme.typography.bodyMedium,
-        )
+            }
+        }
+    }
+}
+
+@Composable
+private fun Dialog(
+    viewModel: BouncerViewModel,
+    dialogFactory: BouncerDialogFactory,
+) {
+    val dialogMessage: String? by viewModel.dialogMessage.collectAsState()
+    var dialog: Dialog? by remember { mutableStateOf(null) }
+
+    if (dialogMessage != null) {
+        if (dialog == null) {
+            dialog =
+                dialogFactory().apply {
+                    setMessage(dialogMessage)
+                    setButton(
+                        DialogInterface.BUTTON_NEUTRAL,
+                        context.getString(R.string.ok),
+                    ) { _, _ ->
+                        viewModel.onDialogDismissed()
+                    }
+                    setCancelable(false)
+                    setCanceledOnTouchOutside(false)
+                    show()
+                }
+        }
+    } else {
+        dialog?.dismiss()
+        dialog = null
     }
 }
 
@@ -420,6 +704,14 @@
     viewModel: BouncerViewModel,
     modifier: Modifier = Modifier,
 ) {
+    if (!viewModel.isUserSwitcherVisible) {
+        // Take up the same space as the user switcher normally would, but with nothing inside it.
+        Box(
+            modifier = modifier,
+        )
+        return
+    }
+
     val selectedUserImage by viewModel.selectedUserImage.collectAsState(null)
     val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList())
 
@@ -539,195 +831,10 @@
     }
 }
 
-/**
- * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable
- * by double-tapping on the side.
- */
-@Composable
-private fun SplitLayout(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
-    modifier: Modifier = Modifier,
-) {
-    SwappableLayout(
-        startContent = { startContentModifier ->
-            StandardLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                layout = BouncerSceneLayout.SPLIT,
-                outputOnly = true,
-                modifier = startContentModifier,
-            )
-        },
-        endContent = { endContentModifier ->
-            UserInputArea(
-                viewModel = viewModel,
-                visibility = UserInputAreaVisibility.INPUT_ONLY,
-                layout = BouncerSceneLayout.SPLIT,
-                modifier = endContentModifier,
-            )
-        },
-        layout = BouncerSceneLayout.SPLIT,
-        modifier = modifier,
-    )
-}
-
-/**
- * Arranges the given two contents side-by-side, supporting a double tap anywhere on the background
- * to flip their positions.
- */
-@Composable
-private fun SwappableLayout(
-    startContent: @Composable (Modifier) -> Unit,
-    endContent: @Composable (Modifier) -> Unit,
-    layout: BouncerSceneLayout,
-    modifier: Modifier = Modifier,
-) {
-    val layoutDirection = LocalLayoutDirection.current
-    val isLeftToRight = layoutDirection == LayoutDirection.Ltr
-    val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) }
-
-    Row(
-        modifier =
-            modifier.pointerInput(Unit) {
-                detectTapGestures(
-                    onDoubleTap = { offset ->
-                        // Depending on where the user double tapped, switch the elements such that
-                        // the endContent is closer to the side that was double tapped.
-                        setSwapped(offset.x < size.width / 2)
-                    }
-                )
-            },
-    ) {
-        val animatedOffset by
-            animateFloatAsState(
-                targetValue =
-                    if (!isSwapped) {
-                        // When startContent is first, both elements have their natural placement so
-                        // they are not offset in any way.
-                        0f
-                    } else if (isLeftToRight) {
-                        // Since startContent is not first, the elements have to be swapped
-                        // horizontally. In the case of LTR locales, this means pushing startContent
-                        // to the right, hence the positive number.
-                        1f
-                    } else {
-                        // Since startContent is not first, the elements have to be swapped
-                        // horizontally. In the case of RTL locales, this means pushing startContent
-                        // to the left, hence the negative number.
-                        -1f
-                    },
-                label = "offset",
-            )
-
-        startContent(
-            Modifier.fillMaxHeight().weight(1f).graphicsLayer {
-                translationX = size.width * animatedOffset
-                alpha = animatedAlpha(animatedOffset)
-            }
-        )
-
-        Box(
-            modifier =
-                Modifier.fillMaxHeight().weight(1f).graphicsLayer {
-                    // A negative sign is used to make sure this is offset in the direction that's
-                    // opposite of the direction that the user switcher is pushed in.
-                    translationX = -size.width * animatedOffset
-                    alpha = animatedAlpha(animatedOffset)
-                }
-        ) {
-            endContent(Modifier.align(layout.swappableEndContentAlignment).widthIn(max = 400.dp))
-        }
-    }
-}
-
-/**
- * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap
- * anywhere on the background to flip their positions.
- *
- * In situations when [isUserSwitcherVisible] is `false`, one of two things may happen: either the
- * UI for the bouncer will be shown on its own, taking up one side, with the other side just being
- * empty space or, if that kind of "stand-alone side-by-side" isn't supported, the standard
- * rendering of the bouncer will be used instead of the side-by-side layout.
- */
-@Composable
-private fun SideBySideLayout(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
-    isUserSwitcherVisible: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    SwappableLayout(
-        startContent = { startContentModifier ->
-            if (isUserSwitcherVisible) {
-                UserSwitcher(
-                    viewModel = viewModel,
-                    modifier = startContentModifier,
-                )
-            } else {
-                Box(
-                    modifier = startContentModifier,
-                )
-            }
-        },
-        endContent = { endContentModifier ->
-            StandardLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                layout = BouncerSceneLayout.SIDE_BY_SIDE,
-                modifier = endContentModifier,
-            )
-        },
-        layout = BouncerSceneLayout.SIDE_BY_SIDE,
-        modifier = modifier,
-    )
-}
-
-/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */
-@Composable
-private fun StackedLayout(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
-    isUserSwitcherVisible: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    Column(
-        modifier = modifier,
-    ) {
-        if (isUserSwitcherVisible) {
-            UserSwitcher(
-                viewModel = viewModel,
-                modifier = Modifier.fillMaxWidth().weight(1f),
-            )
-        }
-
-        StandardLayout(
-            viewModel = viewModel,
-            dialogFactory = dialogFactory,
-            layout = BouncerSceneLayout.STACKED,
-            modifier = Modifier.fillMaxWidth().weight(1f),
-        )
-    }
-}
-
 interface BouncerDialogFactory {
     operator fun invoke(): AlertDialog
 }
 
-/** Enumerates all supported user-input area visibilities. */
-private enum class UserInputAreaVisibility {
-    /**
-     * Only the area where the user enters the input is shown; the area where the input is reflected
-     * back to the user is not shown.
-     */
-    INPUT_ONLY,
-    /**
-     * Only the area where the input is reflected back to the user is shown; the area where the
-     * input is entered by the user is not shown.
-     */
-    OUTPUT_ONLY,
-}
-
 /**
  * Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of
  * the two reaches a stopping point but `0` in the middle of the transition.
@@ -774,48 +881,3 @@
 private val SceneTransitions = transitions {
     from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() }
 }
-
-/** Whether a more compact size should be used for various spacing dimensions. */
-internal val BouncerSceneLayout.isUseCompactSize: Boolean
-    get() =
-        when (this) {
-            BouncerSceneLayout.SIDE_BY_SIDE -> true
-            BouncerSceneLayout.SPLIT -> true
-            else -> false
-        }
-
-/** Amount of space to place between the message and the entered input UI elements, in dips. */
-private val BouncerSceneLayout.spacingBetweenMessageAndEnteredInput: Dp
-    get() =
-        when {
-            this == BouncerSceneLayout.STACKED -> 24.dp
-            isUseCompactSize -> 96.dp
-            else -> 128.dp
-        }
-
-/** Amount of space to place above the topmost UI element, in dips. */
-private val BouncerSceneLayout.topPadding: Dp
-    get() =
-        if (this == BouncerSceneLayout.SPLIT) {
-            40.dp
-        } else {
-            92.dp
-        }
-
-/** Amount of space to place below the bottommost UI element, in dips. */
-private val BouncerSceneLayout.bottomPadding: Dp
-    get() =
-        if (this == BouncerSceneLayout.SPLIT) {
-            40.dp
-        } else {
-            48.dp
-        }
-
-/** The in-a-box alignment for the content on the "end" side of a swappable layout. */
-private val BouncerSceneLayout.swappableEndContentAlignment: Alignment
-    get() =
-        if (this == BouncerSceneLayout.SPLIT) {
-            Alignment.Center
-        } else {
-            Alignment.BottomCenter
-        }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
index 08b7559..1c3d93c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
@@ -26,8 +26,8 @@
 
 /**
  * Returns the [BouncerSceneLayout] that should be used by the bouncer scene. If
- * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.SIDE_BY_SIDE] is replaced by
- * [BouncerSceneLayout.STANDARD].
+ * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.BESIDE_USER_SWITCHER] is replaced by
+ * [BouncerSceneLayout.STANDARD_BOUNCER].
  */
 @Composable
 fun calculateLayout(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index 2799959..0960811 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.bouncer.ui.composable
 
 import android.view.ViewTreeObserver
-import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material3.LocalTextStyle
@@ -31,7 +30,6 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.produceState
 import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.focus.FocusRequester
@@ -81,42 +79,38 @@
         }
     }
 
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        modifier = modifier,
-    ) {
-        val color = MaterialTheme.colorScheme.onSurfaceVariant
-        val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() }
+    val color = MaterialTheme.colorScheme.onSurfaceVariant
+    val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() }
 
-        TextField(
-            value = password,
-            onValueChange = viewModel::onPasswordInputChanged,
-            enabled = isInputEnabled,
-            visualTransformation = PasswordVisualTransformation(),
-            singleLine = true,
-            textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
-            keyboardOptions =
-                KeyboardOptions(
-                    keyboardType = KeyboardType.Password,
-                    imeAction = ImeAction.Done,
-                ),
-            keyboardActions =
-                KeyboardActions(
-                    onDone = { viewModel.onAuthenticateKeyPressed() },
-                ),
-            modifier =
-                Modifier.focusRequester(focusRequester)
-                    .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
-                    .drawBehind {
-                        drawLine(
-                            color = color,
-                            start = Offset(x = 0f, y = size.height - lineWidthPx),
-                            end = Offset(size.width, y = size.height - lineWidthPx),
-                            strokeWidth = lineWidthPx,
-                        )
-                    },
-        )
-    }
+    TextField(
+        value = password,
+        onValueChange = viewModel::onPasswordInputChanged,
+        enabled = isInputEnabled,
+        visualTransformation = PasswordVisualTransformation(),
+        singleLine = true,
+        textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
+        keyboardOptions =
+            KeyboardOptions(
+                keyboardType = KeyboardType.Password,
+                imeAction = ImeAction.Done,
+            ),
+        keyboardActions =
+            KeyboardActions(
+                onDone = { viewModel.onAuthenticateKeyPressed() },
+            ),
+        modifier =
+            modifier
+                .focusRequester(focusRequester)
+                .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
+                .drawBehind {
+                    drawLine(
+                        color = color,
+                        start = Offset(x = 0f, y = size.height - lineWidthPx),
+                        end = Offset(size.width, y = size.height - lineWidthPx),
+                        strokeWidth = lineWidthPx,
+                    )
+                },
+    )
 }
 
 /** Returns a [State] with `true` when the IME/keyboard is visible and `false` when it's not. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index a4b1955..0a5f5d2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -24,6 +24,8 @@
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
@@ -48,7 +50,6 @@
 import com.android.compose.animation.Easings
 import com.android.compose.modifiers.thenIf
 import com.android.internal.R
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
 import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel
 import kotlin.math.min
@@ -61,11 +62,14 @@
  * UI for the input part of a pattern-requiring version of the bouncer.
  *
  * The user can press, hold, and drag their pointer to select dots along a grid of dots.
+ *
+ * If [centerDotsVertically] is `true`, the dots should be centered along the axis of interest; if
+ * `false`, the dots will be pushed towards the end/bottom of the axis.
  */
 @Composable
 internal fun PatternBouncer(
     viewModel: PatternBouncerViewModel,
-    layout: BouncerSceneLayout,
+    centerDotsVertically: Boolean,
     modifier: Modifier = Modifier,
 ) {
     DisposableEffect(Unit) {
@@ -197,6 +201,14 @@
 
     Canvas(
         modifier
+            // Because the width also includes spacing to the left and right of the leftmost and
+            // rightmost dots in the grid and because UX mocks specify the width without that
+            // spacing, the actual width needs to be defined slightly bigger than the UX mock width.
+            .width((262 * colCount / 2).dp)
+            // Because the height also includes spacing above and below the topmost and bottommost
+            // dots in the grid and because UX mocks specify the height without that spacing, the
+            // actual height needs to be defined slightly bigger than the UX mock height.
+            .height((262 * rowCount / 2).dp)
             // Need to clip to bounds to make sure that the lines don't follow the input pointer
             // when it leaves the bounds of the dot grid.
             .clipToBounds()
@@ -260,7 +272,7 @@
                     availableSize = containerSize.height,
                     spacingPerDot = spacing,
                     dotCount = rowCount,
-                    isCentered = layout.isCenteredVertically,
+                    isCentered = centerDotsVertically,
                 )
             offset = Offset(horizontalOffset, verticalOffset)
             scale = (colCount * spacing) / containerSize.width
@@ -423,10 +435,6 @@
     }
 }
 
-/** Whether the UI should be centered vertically. */
-private val BouncerSceneLayout.isCenteredVertically: Boolean
-    get() = this == BouncerSceneLayout.SPLIT
-
 private const val DOT_DIAMETER_DP = 16
 private const val SELECTED_DOT_DIAMETER_DP = 24
 private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 8f5d9f4..f505b90 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -52,7 +52,6 @@
 import com.android.compose.animation.Easings
 import com.android.compose.grid.VerticalGrid
 import com.android.compose.modifiers.thenIf
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
 import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.common.shared.model.ContentDescription
@@ -70,7 +69,7 @@
 @Composable
 fun PinPad(
     viewModel: PinBouncerViewModel,
-    layout: BouncerSceneLayout,
+    verticalSpacing: Dp,
     modifier: Modifier = Modifier,
 ) {
     DisposableEffect(Unit) {
@@ -96,8 +95,8 @@
 
     VerticalGrid(
         columns = columns,
-        verticalSpacing = layout.verticalSpacing,
-        horizontalSpacing = calculateHorizontalSpacingBetweenColumns(layout.gridWidth),
+        verticalSpacing = verticalSpacing,
+        horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp),
         modifier = modifier,
     ) {
         repeat(9) { index ->
@@ -355,14 +354,6 @@
     return (gridWidth - (pinButtonMaxSize * columns)) / (columns - 1)
 }
 
-/** The width of the grid of PIN pad buttons, in dips. */
-private val BouncerSceneLayout.gridWidth: Dp
-    get() = if (isUseCompactSize) 292.dp else 300.dp
-
-/** The spacing between rows of PIN pad buttons, in dips. */
-private val BouncerSceneLayout.verticalSpacing: Dp
-    get() = if (isUseCompactSize) 8.dp else 12.dp
-
 /** Number of columns in the PIN pad grid. */
 private const val columns = 3
 /** Maximum size (width and height) of each PIN pad button. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index c49c197..12f1b30 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -27,11 +27,13 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.boundsInWindow
 import androidx.compose.ui.layout.onPlaced
@@ -89,13 +91,18 @@
     isScrimVisible: Boolean,
     modifier: Modifier = Modifier,
 ) {
+    val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
+
     Box(modifier = modifier) {
         if (isScrimVisible) {
             Box(
                 modifier =
                     Modifier.element(Notifications.Elements.NotificationScrim)
                         .fillMaxSize()
-                        .clip(RoundedCornerShape(32.dp))
+                        .graphicsLayer {
+                            shape = RoundedCornerShape(cornerRadius.dp)
+                            clip = true
+                        }
                         .background(MaterialTheme.colorScheme.surface)
             )
         }
@@ -167,7 +174,9 @@
                     }
                     val boundsInWindow = coordinates.boundsInWindow()
                     viewModel.onBoundsChanged(
+                        left = boundsInWindow.left,
                         top = boundsInWindow.top,
+                        right = boundsInWindow.right,
                         bottom = boundsInWindow.bottom,
                     )
                 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 543b291..695d888 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -465,29 +465,6 @@
     }
 
     @Test
-    fun showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
-        // GIVEN the current security method is SimPin
-        whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
-        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
-            .thenReturn(false)
-        underTest.showSecurityScreen(SecurityMode.SimPin)
-
-        // WHEN a request is made from the SimPin screens to show the next security method
-        whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN)
-        underTest.showNextSecurityScreenOrFinish(
-            /* authenticated= */ true,
-            TARGET_USER_ID,
-            /* bypassSecondaryLockScreen= */ true,
-            SecurityMode.SimPin
-        )
-
-        // THEN the next security method of PIN is set, and the keyguard is not marked as done
-        verify(viewMediatorCallback, never()).keyguardDonePending(anyInt())
-        verify(viewMediatorCallback, never()).keyguardDone(anyInt())
-        Truth.assertThat(underTest.currentSecurityMode).isEqualTo(SecurityMode.PIN)
-    }
-
-    @Test
     fun showNextSecurityScreenOrFinish_DeviceNotSecure() {
         // GIVEN the current security method is SimPin
         whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
@@ -578,6 +555,57 @@
     }
 
     @Test
+    fun showNextSecurityScreenOrFinish_SimPin_Password() {
+        // GIVEN the current security method is SimPin
+        whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+            .thenReturn(false)
+        underTest.showSecurityScreen(SecurityMode.SimPin)
+
+        // WHEN a request is made from the SimPin screens to show the next security method
+        whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+            .thenReturn(SecurityMode.Password)
+        // WHEN security method is SWIPE
+        whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+        whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(false)
+        underTest.showNextSecurityScreenOrFinish(
+            /* authenticated= */ true,
+            TARGET_USER_ID,
+            /* bypassSecondaryLockScreen= */ true,
+            SecurityMode.SimPin
+        )
+
+        // THEN we will not show the password screen.
+        verify(viewFlipperController, never())
+            .getSecurityView(eq(SecurityMode.Password), any(), any())
+    }
+
+    @Test
+    fun showNextSecurityScreenOrFinish_SimPin_SimPin() {
+        // GIVEN the current security method is SimPin
+        whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+            .thenReturn(false)
+        underTest.showSecurityScreen(SecurityMode.SimPin)
+
+        // WHEN a request is made from the SimPin screens to show the next security method
+        whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+            .thenReturn(SecurityMode.SimPin)
+        // WHEN security method is SWIPE
+        whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+        whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(false)
+        underTest.showNextSecurityScreenOrFinish(
+            /* authenticated= */ true,
+            TARGET_USER_ID,
+            /* bypassSecondaryLockScreen= */ true,
+            SecurityMode.SimPin
+        )
+
+        // THEN we will not show the password screen.
+        verify(viewFlipperController).getSecurityView(eq(SecurityMode.SimPin), any(), any())
+    }
+
+    @Test
     fun onSwipeUp_forwardsItToFaceAuthInteractor() {
         val registeredSwipeListener = registeredSwipeListener
         setupGetSecurityView(SecurityMode.Password)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 94c3bde..84d73543 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -34,11 +34,14 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
@@ -63,6 +66,8 @@
     @Mock
     private lateinit var keyguardMessageAreaController:
         KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+    private val updateMonitorCallbackArgumentCaptor =
+        ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
 
     @Before
     fun setup() {
@@ -95,6 +100,9 @@
                 mSelectedUserInteractor
             )
         underTest.init()
+        underTest.onResume(0)
+        verify(keyguardUpdateMonitor)
+            .registerCallback(updateMonitorCallbackArgumentCaptor.capture())
     }
 
     @Test
@@ -111,6 +119,7 @@
 
     @Test
     fun onResume() {
+        reset(keyguardUpdateMonitor)
         underTest.onResume(KeyguardSecurityView.VIEW_REVEALED)
         verify(keyguardUpdateMonitor)
             .registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
@@ -137,4 +146,22 @@
         underTest.resetState()
         verify(keyguardMessageAreaController).setMessage("")
     }
+
+    @Test
+    fun onSimStateChangedFromPinToPuk_showsCurrentSecurityScreen() {
+        updateMonitorCallbackArgumentCaptor.value.onSimStateChanged(
+            /* subId= */ 0,
+            /* slotId= */ 0,
+            TelephonyManager.SIM_STATE_PIN_REQUIRED
+        )
+        verify(keyguardSecurityCallback, never()).showCurrentSecurityScreen()
+
+        updateMonitorCallbackArgumentCaptor.value.onSimStateChanged(
+            /* subId= */ 0,
+            /* slotId= */ 0,
+            TelephonyManager.SIM_STATE_PUK_REQUIRED
+        )
+
+        verify(keyguardSecurityCallback).showCurrentSecurityScreen()
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 2a02164..08cd7ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -21,9 +21,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
@@ -76,12 +76,13 @@
     @Test
     fun authenticate_withCorrectPin_succeeds() =
         testScope.runTest {
-            val throttling by collectLastValue(underTest.throttling)
+            val lockout by collectLastValue(underTest.lockout)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
 
             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
                 .isEqualTo(AuthenticationResult.SUCCEEDED)
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
+            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
         }
 
     @Test
@@ -129,14 +130,15 @@
     @Test
     fun authenticate_withCorrectPassword_succeeds() =
         testScope.runTest {
-            val throttling by collectLastValue(underTest.throttling)
+            val lockout by collectLastValue(underTest.lockout)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
 
             assertThat(underTest.authenticate("password".toList()))
                 .isEqualTo(AuthenticationResult.SUCCEEDED)
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
+            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
         }
 
     @Test
@@ -185,7 +187,7 @@
     fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            val throttling by collectLastValue(underTest.throttling)
+            val lockout by collectLastValue(underTest.lockout)
             utils.authenticationRepository.apply {
                 setAuthenticationMethod(AuthenticationMethodModel.Pin)
                 setAutoConfirmFeatureEnabled(true)
@@ -201,7 +203,8 @@
                     )
                 )
                 .isEqualTo(AuthenticationResult.SKIPPED)
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
+            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
         }
 
     @Test
@@ -262,7 +265,7 @@
         }
 
     @Test
-    fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringThrottling_returnsNull() =
+    fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringLockout_returnsNull() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
             val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
@@ -270,7 +273,7 @@
             utils.authenticationRepository.apply {
                 setAuthenticationMethod(AuthenticationMethodModel.Pin)
                 setAutoConfirmFeatureEnabled(true)
-                setThrottleDuration(42)
+                setLockoutDuration(42)
             }
 
             val authResult =
@@ -331,29 +334,30 @@
         }
 
     @Test
-    fun isAutoConfirmEnabled_featureEnabledButDisabledByThrottling() =
+    fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            val throttling by collectLastValue(underTest.throttling)
+            val lockout by collectLastValue(underTest.lockout)
             utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
 
             // The feature is enabled.
             assertThat(isAutoConfirmEnabled).isTrue()
 
-            // Make many wrong attempts to trigger throttling.
-            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
+            // Make many wrong attempts to trigger lockout.
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
                 underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
             }
-            assertThat(throttling).isNotNull()
+            assertThat(lockout).isNotNull()
+            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
 
-            // Throttling disabled auto-confirm.
+            // Lockout disabled auto-confirm.
             assertThat(isAutoConfirmEnabled).isFalse()
 
-            // Move the clock forward one more second, to completely finish the throttling period:
-            advanceTimeBy(FakeAuthenticationRepository.THROTTLE_DURATION_MS + 1000L)
-            assertThat(throttling).isNull()
+            // Move the clock forward one more second, to completely finish the lockout period:
+            advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_MS + 1000L)
+            assertThat(lockout).isNull()
 
-            // Auto-confirm is still disabled, because throttling occurred at least once in this
+            // Auto-confirm is still disabled, because lockout occurred at least once in this
             // session.
             assertThat(isAutoConfirmEnabled).isFalse()
 
@@ -363,68 +367,70 @@
 
             // Auto-confirm is re-enabled.
             assertThat(isAutoConfirmEnabled).isTrue()
+
+            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
         }
 
     @Test
-    fun throttling() =
+    fun lockout() =
         testScope.runTest {
-            val throttling by collectLastValue(underTest.throttling)
+            val lockout by collectLastValue(underTest.lockout)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
 
-            // Make many wrong attempts, but just shy of what's needed to get throttled:
-            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1) {
+            // Make many wrong attempts, but just shy of what's needed to get locked out:
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
                 underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
-                assertThat(throttling).isNull()
+                assertThat(lockout).isNull()
             }
 
-            // Make one more wrong attempt, leading to throttling:
+            // Make one more wrong attempt, leading to lockout:
             underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
-            assertThat(throttling)
+            assertThat(lockout)
                 .isEqualTo(
-                    AuthenticationThrottlingModel(
+                    AuthenticationLockoutModel(
                         failedAttemptCount =
-                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
-                        remainingSeconds = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS,
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
+                        remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
                     )
                 )
+            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
 
-            // Correct PIN, but throttled, so doesn't attempt it:
+            // Correct PIN, but locked out, so doesn't attempt it:
             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
                 .isEqualTo(AuthenticationResult.SKIPPED)
-            assertThat(throttling)
+            assertThat(lockout)
                 .isEqualTo(
-                    AuthenticationThrottlingModel(
+                    AuthenticationLockoutModel(
                         failedAttemptCount =
-                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
-                        remainingSeconds = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS,
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
+                        remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
                     )
                 )
 
-            // Move the clock forward to ALMOST skip the throttling, leaving one second to go:
-            val throttleTimeoutSec = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS
-            repeat(FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS - 1) { time ->
+            // Move the clock forward to ALMOST skip the lockout, leaving one second to go:
+            val lockoutTimeoutSec = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS
+            repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - 1) { time ->
                 advanceTimeBy(1000)
-                assertThat(throttling)
+                assertThat(lockout)
                     .isEqualTo(
-                        AuthenticationThrottlingModel(
+                        AuthenticationLockoutModel(
                             failedAttemptCount =
-                                FakeAuthenticationRepository
-                                    .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
-                            remainingSeconds = throttleTimeoutSec - (time + 1),
+                                FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
+                            remainingSeconds = lockoutTimeoutSec - (time + 1),
                         )
                     )
             }
 
-            // Move the clock forward one more second, to completely finish the throttling period:
+            // Move the clock forward one more second, to completely finish the lockout period:
             advanceTimeBy(1000)
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
 
-            // Correct PIN and no longer throttled so unlocks successfully:
+            // Correct PIN and no longer locked out so unlocks successfully:
             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
                 .isEqualTo(AuthenticationResult.SUCCEEDED)
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index cbb772f..0ab596c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -21,6 +21,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
+import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
@@ -56,10 +58,12 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -539,4 +543,77 @@
                 .onDozeAmountChanged(eq(0f), eq(0f), eq(UdfpsKeyguardViewLegacy.ANIMATION_NONE))
             job.cancel()
         }
+
+    @Test
+    fun cancelledLockscreenToAod_dozeAmountNotUpdatedToZero() =
+        testScope.runTest {
+            // GIVEN view is attached
+            mController.onViewAttached()
+            Mockito.reset(mView)
+
+            val job = mController.listenForLockscreenAodTransitions(this)
+            // WHEN lockscreen to aod transition is cancelled
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.CANCELED
+                )
+            )
+            runCurrent()
+
+            // THEN doze amount is NOT updated to zero
+            verify(mView, never()).onDozeAmountChanged(eq(0f), eq(0f), anyInt())
+            job.cancel()
+        }
+
+    @Test
+    fun dreamingToAod_dozeAmountChanged() =
+        testScope.runTest {
+            // GIVEN view is attached
+            mController.onViewAttached()
+            Mockito.reset(mView)
+
+            val job = mController.listenForDreamingToAodTransitions(this)
+            // WHEN dreaming to aod transition in progress
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.AOD,
+                    value = .3f,
+                    transitionState = TransitionState.RUNNING
+                )
+            )
+            runCurrent()
+
+            // THEN doze amount is updated to
+            verify(mView).onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATE_APPEAR_ON_SCREEN_OFF))
+            job.cancel()
+        }
+
+    @Test
+    fun alternateBouncerToAod_dozeAmountChanged() =
+        testScope.runTest {
+            // GIVEN view is attached
+            mController.onViewAttached()
+            Mockito.reset(mView)
+
+            val job = mController.listenForAlternateBouncerToAodTransitions(this)
+            // WHEN alternate bouncer to aod transition in progress
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.AOD,
+                    value = .3f,
+                    transitionState = TransitionState.RUNNING
+                )
+            )
+            runCurrent()
+
+            // THEN doze amount is updated to
+            verify(mView)
+                .onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN))
+            job.cancel()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 3e3a1a9..9b1df7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -21,9 +21,9 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.res.R
@@ -246,46 +246,42 @@
         }
 
     @Test
-    fun throttling() =
+    fun lockout() =
         testScope.runTest {
-            val throttling by collectLastValue(underTest.throttling)
+            val lockout by collectLastValue(underTest.lockout)
             val message by collectLastValue(underTest.message)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            assertThat(throttling).isNull()
-            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times ->
+            assertThat(lockout).isNull()
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
                 // Wrong PIN.
                 assertThat(underTest.authenticate(listOf(6, 7, 8, 9)))
                     .isEqualTo(AuthenticationResult.FAILED)
-                if (
-                    times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
-                ) {
+                if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
                     assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
                 }
             }
-            assertThat(throttling)
+            assertThat(lockout)
                 .isEqualTo(
-                    AuthenticationThrottlingModel(
+                    AuthenticationLockoutModel(
                         failedAttemptCount =
-                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
-                        remainingSeconds = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS,
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
+                        remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
                     )
                 )
             assertTryAgainMessage(
                 message,
-                FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
-                    .toInt()
+                FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt()
             )
 
-            // Correct PIN, but throttled, so doesn't change away from the bouncer scene:
+            // Correct PIN, but locked out, so doesn't change away from the bouncer scene:
             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
                 .isEqualTo(AuthenticationResult.SKIPPED)
             assertTryAgainMessage(
                 message,
-                FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
-                    .toInt()
+                FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt()
             )
 
-            throttling?.remainingSeconds?.let { seconds ->
+            lockout?.remainingSeconds?.let { seconds ->
                 repeat(seconds) { time ->
                     advanceTimeBy(1000)
                     val remainingTimeSec = seconds - time - 1
@@ -295,12 +291,12 @@
                 }
             }
             assertThat(message).isEqualTo("")
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
 
-            // Correct PIN and no longer throttled so changes to the Gone scene:
+            // Correct PIN and no longer locked out so changes to the Gone scene:
             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
                 .isEqualTo(AuthenticationResult.SUCCEEDED)
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 45c186d..2f0843b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -35,7 +35,6 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
     private val bouncerInteractor =
         utils.bouncerInteractor(
             authenticationInteractor = utils.authenticationInteractor(),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 2b64d8e..16a9359 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -135,17 +135,17 @@
     fun message() =
         testScope.runTest {
             val message by collectLastValue(underTest.message)
-            val throttling by collectLastValue(bouncerInteractor.throttling)
+            val lockout by collectLastValue(bouncerInteractor.lockout)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(message?.isUpdateAnimated).isTrue()
 
-            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
                 // Wrong PIN.
                 bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
             }
             assertThat(message?.isUpdateAnimated).isFalse()
 
-            throttling?.remainingSeconds?.let { remainingSeconds ->
+            lockout?.remainingSeconds?.let { remainingSeconds ->
                 advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds)
             }
             assertThat(message?.isUpdateAnimated).isTrue()
@@ -160,37 +160,37 @@
                         authViewModel?.isInputEnabled ?: emptyFlow()
                     }
                 )
-            val throttling by collectLastValue(bouncerInteractor.throttling)
+            val lockout by collectLastValue(bouncerInteractor.lockout)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(isInputEnabled).isTrue()
 
-            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
                 // Wrong PIN.
                 bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
             }
             assertThat(isInputEnabled).isFalse()
 
-            throttling?.remainingSeconds?.let { remainingSeconds ->
+            lockout?.remainingSeconds?.let { remainingSeconds ->
                 advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds)
             }
             assertThat(isInputEnabled).isTrue()
         }
 
     @Test
-    fun throttlingDialogMessage() =
+    fun dialogMessage() =
         testScope.runTest {
-            val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage)
+            val dialogMessage by collectLastValue(underTest.dialogMessage)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
 
-            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
                 // Wrong PIN.
-                assertThat(throttlingDialogMessage).isNull()
+                assertThat(dialogMessage).isNull()
                 bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
             }
-            assertThat(throttlingDialogMessage).isNotEmpty()
+            assertThat(dialogMessage).isNotEmpty()
 
-            underTest.onThrottlingDialogDismissed()
-            assertThat(throttlingDialogMessage).isNull()
+            underTest.onDialogDismissed()
+            assertThat(dialogMessage).isNull()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index a217d93..6d6baa5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -19,8 +19,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.res.R
@@ -243,12 +243,12 @@
         }
 
     @Test
-    fun onImeVisibilityChanged_falseAfterTrue_whileThrottling_doesNothing() =
+    fun onImeVisibilityChanged_falseAfterTrue_whileLockedOut_doesNothing() =
         testScope.runTest {
             val events by collectValues(bouncerInteractor.onImeHiddenByUser)
             assertThat(events).isEmpty()
             underTest.onImeVisibilityChanged(isVisible = true)
-            setThrottling(true)
+            setLockout(true)
 
             underTest.onImeVisibilityChanged(isVisible = false)
 
@@ -284,11 +284,11 @@
         }
 
     @Test
-    fun isTextFieldFocusRequested_focusLostWhileThrottling_staysFalse() =
+    fun isTextFieldFocusRequested_focusLostWhileLockedOut_staysFalse() =
         testScope.runTest {
             val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested)
             underTest.onTextFieldFocusChanged(isFocused = true)
-            setThrottling(true)
+            setLockout(true)
 
             underTest.onTextFieldFocusChanged(isFocused = false)
 
@@ -296,14 +296,14 @@
         }
 
     @Test
-    fun isTextFieldFocusRequested_throttlingCountdownEnds_becomesTrue() =
+    fun isTextFieldFocusRequested_lockoutCountdownEnds_becomesTrue() =
         testScope.runTest {
             val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested)
             underTest.onTextFieldFocusChanged(isFocused = true)
-            setThrottling(true)
+            setLockout(true)
             underTest.onTextFieldFocusChanged(isFocused = false)
 
-            setThrottling(false)
+            setLockout(false)
 
             assertThat(isTextFieldFocusRequested).isTrue()
         }
@@ -327,24 +327,24 @@
         switchToScene(SceneKey.Bouncer)
     }
 
-    private suspend fun TestScope.setThrottling(
-        isThrottling: Boolean,
+    private suspend fun TestScope.setLockout(
+        isLockedOut: Boolean,
         failedAttemptCount: Int = 5,
     ) {
-        if (isThrottling) {
+        if (isLockedOut) {
             repeat(failedAttemptCount) {
                 authenticationRepository.reportAuthenticationAttempt(false)
             }
             val remainingTimeSeconds = 30
-            authenticationRepository.setThrottleDuration(remainingTimeSeconds * 1000)
-            authenticationRepository.throttling.value =
-                AuthenticationThrottlingModel(
+            authenticationRepository.setLockoutDuration(remainingTimeSeconds * 1000)
+            authenticationRepository.lockout.value =
+                AuthenticationLockoutModel(
                     failedAttemptCount = failedAttemptCount,
                     remainingSeconds = remainingTimeSeconds,
                 )
         } else {
             authenticationRepository.reportAuthenticationAttempt(true)
-            authenticationRepository.throttling.value = null
+            authenticationRepository.lockout.value = null
         }
 
         runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 862c39c..8971423 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -304,13 +304,12 @@
     fun onDragEnd_whenPatternTooShort() =
         testScope.runTest {
             val message by collectLastValue(bouncerViewModel.message)
-            val throttlingDialogMessage by
-                collectLastValue(bouncerViewModel.throttlingDialogMessage)
+            val dialogMessage by collectLastValue(bouncerViewModel.dialogMessage)
             lockDeviceAndOpenPatternBouncer()
 
             // Enter a pattern that's too short more than enough times that would normally trigger
-            // throttling if the pattern were not too short and wrong:
-            val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING + 1
+            // lockout if the pattern were not too short and wrong:
+            val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT + 1
             repeat(attempts) { attempt ->
                 underTest.onDragStart()
                 CORRECT_PATTERN.subList(
@@ -328,7 +327,7 @@
                 underTest.onDragEnd()
 
                 assertWithMessage("Attempt #$attempt").that(message?.text).isEqualTo(WRONG_PATTERN)
-                assertWithMessage("Attempt #$attempt").that(throttlingDialogMessage).isNull()
+                assertWithMessage("Attempt #$attempt").that(dialogMessage).isNull()
             }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index bc4bae0..34f703b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -49,8 +49,10 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -93,6 +95,8 @@
     private lateinit var dockManager: DockManagerFake
     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
 
+    private val kosmos = testKosmos()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -179,6 +183,7 @@
         underTest =
             KeyguardQuickAffordanceInteractor(
                 keyguardInteractor = withDeps.keyguardInteractor,
+                shadeInteractor = kosmos.shadeInteractor,
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
@@ -339,6 +344,31 @@
         }
 
     @Test
+    fun quickAffordanceAlwaysVisible_notVisible_restrictedByPolicyManager() =
+        testScope.runTest {
+            whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
+                .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
+
+            repository.setKeyguardShowing(false)
+            repository.setIsDozing(true)
+            homeControls.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    icon = ICON,
+                    activationState = ActivationState.Active,
+                )
+            )
+
+            val collectedValue by
+                collectLastValue(
+                    underTest.quickAffordanceAlwaysVisible(
+                        KeyguardQuickAffordancePosition.BOTTOM_START
+                    )
+                )
+
+            assertThat(collectedValue).isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java)
+        }
+
+    @Test
     fun quickAffordanceAlwaysVisible_evenWhenLockScreenNotShowingAndDozing() =
         testScope.runTest {
             repository.setKeyguardShowing(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
new file mode 100644
index 0000000..4b96251
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.qs.tiles.impl.saver.domain
+
+import android.content.SharedPreferences
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.leaks.FakeDataSaverController
+import kotlin.coroutines.EmptyCoroutineContext
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+
+/** Test [DataSaverDialogDelegate]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DataSaverDialogDelegateTest : SysuiTestCase() {
+
+    private val dataSaverController = FakeDataSaverController(LeakCheck())
+
+    private lateinit var sysuiDialogFactory: SystemUIDialog.Factory
+    private lateinit var sysuiDialog: SystemUIDialog
+    private lateinit var dataSaverDialogDelegate: DataSaverDialogDelegate
+
+    @Before
+    fun setup() {
+        sysuiDialog = mock<SystemUIDialog>()
+        sysuiDialogFactory = mock<SystemUIDialog.Factory>()
+
+        dataSaverDialogDelegate =
+            DataSaverDialogDelegate(
+                sysuiDialogFactory,
+                context,
+                EmptyCoroutineContext,
+                dataSaverController,
+                mock<SharedPreferences>()
+            )
+
+        whenever(sysuiDialogFactory.create(eq(dataSaverDialogDelegate), eq(context)))
+            .thenReturn(sysuiDialog)
+    }
+    @Test
+    fun delegateSetsDialogTitleCorrectly() {
+        val expectedResId = R.string.data_saver_enable_title
+
+        dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+
+        verify(sysuiDialog).setTitle(eq(expectedResId))
+    }
+
+    @Test
+    fun delegateSetsDialogMessageCorrectly() {
+        val expectedResId = R.string.data_saver_description
+
+        dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+
+        verify(sysuiDialog).setMessage(expectedResId)
+    }
+
+    @Test
+    fun delegateSetsDialogPositiveButtonCorrectly() {
+        val expectedResId = R.string.data_saver_enable_button
+
+        dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+
+        verify(sysuiDialog).setPositiveButton(eq(expectedResId), any())
+    }
+
+    @Test
+    fun delegateSetsDialogCancelButtonCorrectly() {
+        val expectedResId = R.string.cancel
+
+        dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+
+        verify(sysuiDialog).setNeutralButton(eq(expectedResId), eq(null))
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
new file mode 100644
index 0000000..d182412
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.qs.tiles.impl.saver.domain
+
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.qs.tiles.impl.saver.qsDataSaverTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DataSaverTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val dataSaverTileConfig = kosmos.qsDataSaverTileConfig
+
+    // Using lazy (versus =) to make sure we override the right context -- see b/311612168
+    private val mapper by lazy { DataSaverTileMapper(context.orCreateTestableResources.resources) }
+
+    @Test
+    fun activeStateMatchesEnabledModel() {
+        val inputModel = DataSaverTileModel(true)
+
+        val outputState = mapper.map(dataSaverTileConfig, inputModel)
+
+        val expectedState =
+            createDataSaverTileState(
+                QSTileState.ActivationState.ACTIVE,
+                R.drawable.qs_data_saver_icon_on
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun inactiveStateMatchesDisabledModel() {
+        val inputModel = DataSaverTileModel(false)
+
+        val outputState = mapper.map(dataSaverTileConfig, inputModel)
+
+        val expectedState =
+            createDataSaverTileState(
+                QSTileState.ActivationState.INACTIVE,
+                R.drawable.qs_data_saver_icon_off
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createDataSaverTileState(
+        activationState: QSTileState.ActivationState,
+        iconRes: Int
+    ): QSTileState {
+        val label = context.getString(R.string.data_saver)
+        val secondaryLabel =
+            if (activationState == QSTileState.ActivationState.ACTIVE)
+                context.resources.getStringArray(R.array.tile_states_saver)[2]
+            else if (activationState == QSTileState.ActivationState.INACTIVE)
+                context.resources.getStringArray(R.array.tile_states_saver)[1]
+            else context.resources.getStringArray(R.array.tile_states_saver)[0]
+
+        return QSTileState(
+            { Icon.Resource(iconRes, null) },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            label,
+            null,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt
new file mode 100644
index 0000000..819bd03
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.qs.tiles.impl.saver.domain.interactor
+
+import android.os.UserHandle
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.utils.leaks.FakeDataSaverController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DataSaverTileDataInteractorTest : SysuiTestCase() {
+    private val controller: FakeDataSaverController = FakeDataSaverController(LeakCheck())
+    private val underTest: DataSaverTileDataInteractor = DataSaverTileDataInteractor(controller)
+
+    @Test
+    fun isAvailableRegardlessOfController() = runTest {
+        controller.setDataSaverEnabled(false)
+
+        runCurrent()
+        val availability by collectLastValue(underTest.availability(TEST_USER))
+
+        Truth.assertThat(availability).isTrue()
+    }
+
+    @Test
+    fun dataMatchesController() = runTest {
+        controller.setDataSaverEnabled(false)
+        val flowValues: List<DataSaverTileModel> by
+            collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+        runCurrent()
+        controller.setDataSaverEnabled(true)
+        runCurrent()
+        controller.setDataSaverEnabled(false)
+        runCurrent()
+
+        Truth.assertThat(flowValues.size).isEqualTo(3)
+        Truth.assertThat(flowValues.map { it.isEnabled })
+            .containsExactly(false, true, false)
+            .inOrder()
+    }
+
+    private companion object {
+        val TEST_USER = UserHandle.of(1)!!
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..7091cb3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt
@@ -0,0 +1,183 @@
+/*
+ * 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.qs.tiles.impl.saver.domain.interactor
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.leaks.FakeDataSaverController
+import com.google.common.truth.Truth.assertThat
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DataSaverTileUserActionInteractorTest : SysuiTestCase() {
+    private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+    private val dataSaverController = FakeDataSaverController(LeakCheck())
+
+    private lateinit var userFileManager: UserFileManager
+    private lateinit var sharedPreferences: SharedPreferences
+    private lateinit var dialogFactory: SystemUIDialog.Factory
+    private lateinit var underTest: DataSaverTileUserActionInteractor
+
+    @Before
+    fun setup() {
+        userFileManager = mock<UserFileManager>()
+        sharedPreferences = mock<SharedPreferences>()
+        dialogFactory = mock<SystemUIDialog.Factory>()
+        whenever(
+                userFileManager.getSharedPreferences(
+                    eq(DataSaverTileUserActionInteractor.PREFS),
+                    eq(Context.MODE_PRIVATE),
+                    eq(context.userId)
+                )
+            )
+            .thenReturn(sharedPreferences)
+
+        underTest =
+            DataSaverTileUserActionInteractor(
+                context,
+                EmptyCoroutineContext,
+                EmptyCoroutineContext,
+                dataSaverController,
+                qsTileIntentUserActionHandler,
+                mock<DialogLaunchAnimator>(),
+                dialogFactory,
+                userFileManager,
+            )
+    }
+
+    /** Since the dialog was shown before, we expect the click to enable the controller. */
+    @Test
+    fun handleClickToEnableDialogShownBefore() = runTest {
+        whenever(
+                sharedPreferences.getBoolean(
+                    eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
+                    any()
+                )
+            )
+            .thenReturn(true)
+        val stateBeforeClick = false
+
+        underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(stateBeforeClick)))
+
+        assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!stateBeforeClick)
+    }
+
+    /**
+     * The first time the tile is clicked to turn on we expect (1) the enabled state to not change
+     * and (2) the dialog to be shown instead.
+     */
+    @Test
+    fun handleClickToEnableDialogNotShownBefore() = runTest {
+        whenever(
+                sharedPreferences.getBoolean(
+                    eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
+                    any()
+                )
+            )
+            .thenReturn(false)
+        val mockDialog = mock<SystemUIDialog>()
+        whenever(dialogFactory.create(any(), any())).thenReturn(mockDialog)
+        val stateBeforeClick = false
+
+        val input = QSTileInputTestKtx.click(DataSaverTileModel(stateBeforeClick))
+        underTest.handleInput(input)
+
+        assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(stateBeforeClick)
+        verify(mockDialog).show()
+    }
+
+    /** Disabling should flip the state, even if the dialog was not shown before. */
+    @Test
+    fun handleClickToDisableDialogNotShownBefore() = runTest {
+        whenever(
+                sharedPreferences.getBoolean(
+                    eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
+                    any()
+                )
+            )
+            .thenReturn(false)
+        val enabledBeforeClick = true
+
+        underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(enabledBeforeClick)))
+
+        assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!enabledBeforeClick)
+    }
+
+    @Test
+    fun handleClickToDisableDialogShownBefore() = runTest {
+        whenever(
+                sharedPreferences.getBoolean(
+                    eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
+                    any()
+                )
+            )
+            .thenReturn(true)
+        val enabledBeforeClick = true
+
+        underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(enabledBeforeClick)))
+
+        assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!enabledBeforeClick)
+    }
+
+    @Test
+    fun handleLongClickWhenEnabled() = runTest {
+        val enabledState = true
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(DataSaverTileModel(enabledState)))
+
+        assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+        val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+        val actualIntentAction = intentInput.intent.action
+        val expectedIntentAction = Settings.ACTION_DATA_SAVER_SETTINGS
+        assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+    }
+
+    @Test
+    fun handleLongClickWhenDisabled() = runTest {
+        val enabledState = false
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(DataSaverTileModel(enabledState)))
+
+        assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+        val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+        val actualIntentAction = intentInput.intent.action
+        val expectedIntentAction = Settings.ACTION_DATA_SAVER_SETTINGS
+        assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 28f3e93..4cdb08a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -67,9 +67,24 @@
             val bounds by collectLastValue(appearanceViewModel.stackBounds)
 
             val top = 200f
+            val left = 0f
             val bottom = 550f
-            placeholderViewModel.onBoundsChanged(top, bottom)
-            assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom))
+            val right = 100f
+            placeholderViewModel.onBoundsChanged(
+                left = left,
+                top = top,
+                right = right,
+                bottom = bottom
+            )
+            assertThat(bounds)
+                .isEqualTo(
+                    NotificationContainerBounds(
+                        left = left,
+                        top = top,
+                        right = right,
+                        bottom = bottom
+                    )
+                )
         }
 
     @Test
diff --git a/packages/SystemUI/res/drawable/qs_record_issue_icon_off.xml b/packages/SystemUI/res/drawable/qs_record_issue_icon_off.xml
new file mode 100644
index 0000000..bd60431
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_record_issue_icon_off.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24.0dp"
+    android:height="24.0dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:pathData="M5.76 12.89c0 3.28 2.54 5.97 5.76 6.22v-8.26h-5.4c-.23.64-.36 1.33-.36 2.04zm9.27-5.45l1.01-1.59c.19-.29.1-.67-.19-.86-.29-.19-.68-.1-.86.19l-1.12 1.76c-.59-.19-1.22-.29-1.87-.29s-1.28.1-1.87.29L9.01 5.18c-.18-.29-.57-.38-.86-.19-.29.18-.38.57-.19.86l1.01 1.59c-1.02.57-1.86 1.43-2.43 2.45h10.92c-.57-1.02-1.41-1.88-2.43-2.45zm2.85 3.41h-5.4v8.26c3.22-.25 5.76-2.93 5.76-6.22 0-.71-.13-1.4-.36-2.04z"
+        android:fillColor="#000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/qs_record_issue_icon_on.xml b/packages/SystemUI/res/drawable/qs_record_issue_icon_on.xml
new file mode 100644
index 0000000..fadaf78
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_record_issue_icon_on.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24.0dp"
+    android:height="24.0dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:pathData="M5.76 12.89c0 3.28 2.54 5.97 5.76 6.22v-8.26h-5.4c-.23.64-.36 1.33-.36 2.04zm9.27-5.45l1.01-1.59c.19-.29.1-.67-.19-.86-.29-.19-.68-.1-.86.19l-1.12 1.76c-.59-.19-1.22-.29-1.87-.29s-1.28.1-1.87.29L9.01 5.18c-.18-.29-.57-.38-.86-.19-.29.18-.38.57-.19.86l1.01 1.59c-1.02.57-1.86 1.43-2.43 2.45h10.92c-.57-1.02-1.41-1.88-2.43-2.45zm2.85 3.41h-5.4v8.26c3.22-.25 5.76-2.93 5.76-6.22 0-.71-.13-1.4-.36-2.04z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 66c57fc..6d7ce06 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -106,5 +106,5 @@
     </FrameLayout>
 
     <include layout="@layout/ambient_indication"
-             android:id="@+id/ambient_indication_container" />
+             android:id="@id/ambient_indication_container" />
 </com.android.systemui.statusbar.phone.KeyguardBottomAreaView>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 1838795..cf63cc7 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -223,6 +223,8 @@
     <item type="id" name="lock_icon_bg" />
     <item type="id" name="burn_in_layer" />
     <item type="id" name="communal_tutorial_indicator" />
+    <item type="id" name="nssl_placeholder_barrier_bottom" />
+    <item type="id" name="ambient_indication_container" />
 
     <!-- Privacy dialog -->
     <item type="id" name="privacy_dialog_close_app_button" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7ca0b6e..78b701c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -824,6 +824,13 @@
     <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
     <string name="quick_settings_screen_record_stop">Stop</string>
 
+    <!-- QuickSettings: Record Issue tile [CHAR LIMIT=NONE] -->
+    <string name="qs_record_issue_label">Record Issue</string>
+    <!-- QuickSettings: Text to prompt the user to begin a new recording [CHAR LIMIT=20] -->
+    <string name="qs_record_issue_start">Start</string>
+    <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
+    <string name="qs_record_issue_stop">Stop</string>
+
     <!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_onehanded_label">One-handed mode</string>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index c505bd5..df7182b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -21,6 +21,7 @@
 import android.text.TextUtils;
 import android.view.View;
 
+import com.android.internal.jank.Cuj;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
 
@@ -29,40 +30,25 @@
 
 public final class InteractionJankMonitorWrapper {
     // Launcher journeys.
-    public static final int CUJ_APP_LAUNCH_FROM_RECENTS =
-            InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS;
-    public static final int CUJ_APP_LAUNCH_FROM_ICON =
-            InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON;
-    public static final int CUJ_APP_CLOSE_TO_HOME =
-            InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
+    public static final int CUJ_APP_LAUNCH_FROM_RECENTS = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS;
+    public static final int CUJ_APP_LAUNCH_FROM_ICON = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON;
+    public static final int CUJ_APP_CLOSE_TO_HOME = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
     public static final int CUJ_APP_CLOSE_TO_HOME_FALLBACK =
-            InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
-    public static final int CUJ_APP_CLOSE_TO_PIP =
-            InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP;
-    public static final int CUJ_QUICK_SWITCH =
-            InteractionJankMonitor.CUJ_LAUNCHER_QUICK_SWITCH;
-    public static final int CUJ_OPEN_ALL_APPS =
-            InteractionJankMonitor.CUJ_LAUNCHER_OPEN_ALL_APPS;
-    public static final int CUJ_CLOSE_ALL_APPS_SWIPE =
-            InteractionJankMonitor.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE;
-    public static final int CUJ_CLOSE_ALL_APPS_TO_HOME =
-            InteractionJankMonitor.CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
-    public static final int CUJ_ALL_APPS_SCROLL =
-            InteractionJankMonitor.CUJ_LAUNCHER_ALL_APPS_SCROLL;
-    public static final int CUJ_APP_LAUNCH_FROM_WIDGET =
-            InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET;
-    public static final int CUJ_SPLIT_SCREEN_ENTER =
-            InteractionJankMonitor.CUJ_SPLIT_SCREEN_ENTER;
+            Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
+    public static final int CUJ_APP_CLOSE_TO_PIP = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_PIP;
+    public static final int CUJ_QUICK_SWITCH = Cuj.CUJ_LAUNCHER_QUICK_SWITCH;
+    public static final int CUJ_OPEN_ALL_APPS = Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS;
+    public static final int CUJ_CLOSE_ALL_APPS_SWIPE = Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE;
+    public static final int CUJ_CLOSE_ALL_APPS_TO_HOME = Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
+    public static final int CUJ_ALL_APPS_SCROLL = Cuj.CUJ_LAUNCHER_ALL_APPS_SCROLL;
+    public static final int CUJ_APP_LAUNCH_FROM_WIDGET = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET;
+    public static final int CUJ_SPLIT_SCREEN_ENTER = Cuj.CUJ_SPLIT_SCREEN_ENTER;
     public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION =
-            InteractionJankMonitor.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
-    public static final int CUJ_RECENTS_SCROLLING =
-            InteractionJankMonitor.CUJ_RECENTS_SCROLLING;
-    public static final int CUJ_APP_SWIPE_TO_RECENTS =
-            InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
-    public static final int CUJ_OPEN_SEARCH_RESULT =
-            InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
-    public static final int CUJ_LAUNCHER_UNFOLD_ANIM =
-            InteractionJankMonitor.CUJ_LAUNCHER_UNFOLD_ANIM;
+            Cuj.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
+    public static final int CUJ_RECENTS_SCROLLING = Cuj.CUJ_RECENTS_SCROLLING;
+    public static final int CUJ_APP_SWIPE_TO_RECENTS = Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
+    public static final int CUJ_OPEN_SEARCH_RESULT = Cuj.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+    public static final int CUJ_LAUNCHER_UNFOLD_ANIM = Cuj.CUJ_LAUNCHER_UNFOLD_ANIM;
 
     @IntDef({
             CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -89,7 +75,7 @@
      * Begin a trace session.
      *
      * @param v       an attached view.
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link Cuj.CujType}.
      */
     public static void begin(View v, @CujType int cujType) {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return;
@@ -100,7 +86,7 @@
      * Begin a trace session.
      *
      * @param v       an attached view.
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link Cuj.CujType}.
      * @param timeout duration to cancel the instrumentation in ms
      */
     public static void begin(View v, @CujType int cujType, long timeout) {
@@ -115,7 +101,7 @@
      * Begin a trace session.
      *
      * @param v       an attached view.
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link Cuj.CujType}.
      * @param tag the tag to distinguish different flow of same type CUJ.
      */
     public static void begin(View v, @CujType int cujType, String tag) {
@@ -131,7 +117,7 @@
     /**
      * End a trace session.
      *
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link Cuj.CujType}.
      */
     public static void end(@CujType int cujType) {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
index 38a8cd3..c4aa7a2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
@@ -104,4 +104,14 @@
      */
     default void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
     }
+
+    /**
+     * Shows the security screen that should be shown.
+     *
+     * This can be considered as a "refresh" of the bouncer view. Based on certain parameters,
+     * we might switch to a different bouncer screen. e.g. SimPin to SimPuk.
+     */
+    default void showCurrentSecurityScreen() {
+
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index f706301..0a4378e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -27,6 +27,8 @@
 import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_PRIMARY;
 import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER;
 import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE;
+import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPin;
+import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPuk;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
 import static com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES;
@@ -99,6 +101,7 @@
 import dagger.Lazy;
 
 import java.io.File;
+import java.util.Arrays;
 import java.util.Optional;
 
 import javax.inject.Inject;
@@ -164,8 +167,8 @@
                     }
                     mCurrentUser = mSelectedUserInteractor.getSelectedUserId();
                     showPrimarySecurityScreen(false);
-                    if (mCurrentSecurityMode != SecurityMode.SimPin
-                            && mCurrentSecurityMode != SecurityMode.SimPuk) {
+                    if (mCurrentSecurityMode != SimPin
+                            && mCurrentSecurityMode != SimPuk) {
                         reinflateViewFlipper((l) -> {
                         });
                     }
@@ -334,6 +337,11 @@
         public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
             mViewMediatorCallback.setNeedsInput(needsInput);
         }
+
+        @Override
+        public void showCurrentSecurityScreen() {
+            showPrimarySecurityScreen(false);
+        }
     };
 
     private final SwipeListener mSwipeListener = new SwipeListener() {
@@ -888,7 +896,8 @@
                         finish = true;
                         eventSubtype = BOUNCER_DISMISS_SIM;
                         uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
-                    } else {
+                    } else if (Arrays.asList(SimPin, SimPuk).contains(securityMode)) {
+                        // There are additional screens to the sim pin/puk flow.
                         showSecurityScreen(securityMode);
                     }
                     break;
@@ -1095,8 +1104,8 @@
     }
 
     private void configureMode() {
-        boolean useSimSecurity = mCurrentSecurityMode == SecurityMode.SimPin
-                || mCurrentSecurityMode == SecurityMode.SimPuk;
+        boolean useSimSecurity = mCurrentSecurityMode == SimPin
+                || mCurrentSecurityMode == SimPuk;
         int mode = KeyguardSecurityContainer.MODE_DEFAULT;
         if (canDisplayUserSwitcher() && !useSimSecurity) {
             mode = KeyguardSecurityContainer.MODE_USER_SWITCHER;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 6e24208..c5e7070 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
 import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
 
 import android.annotation.NonNull;
@@ -60,7 +62,7 @@
     // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would
     // be displayed to inform user about the number of remaining PIN attempts left.
     private boolean mShowDefaultMessage;
-    private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private int mSubId = INVALID_SUBSCRIPTION_ID;
     private AlertDialog mRemainingAttemptsDialog;
     private ImageView mSimImageView;
 
@@ -68,6 +70,12 @@
         @Override
         public void onSimStateChanged(int subId, int slotId, int simState) {
             if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
+            // If subId has gone to PUK required then we need to go to the PUK screen.
+            if (subId == mSubId && simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) {
+                getKeyguardSecurityCallback().showCurrentSecurityScreen();
+                return;
+            }
+
             if (simState == TelephonyManager.SIM_STATE_READY) {
                 mRemainingAttempts = -1;
                 resetState();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 37bd9b2..9c61a8a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1277,6 +1277,17 @@
 
     private final FaceAuthenticationListener mFaceAuthenticationListener =
             new FaceAuthenticationListener() {
+                public void onAuthenticatedChanged(boolean isAuthenticated) {
+                    if (!isAuthenticated) {
+                        for (int i = 0; i < mCallbacks.size(); i++) {
+                            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+                            if (cb != null) {
+                                cb.onFacesCleared();
+                            }
+                        }
+                    }
+                }
+
                 @Override
                 public void onAuthEnrollmentStateChanged(boolean enrolled) {
                     notifyAboutEnrollmentChange(TYPE_FACE);
@@ -1961,7 +1972,7 @@
 
     protected void handleStartedGoingToSleep(int arg1) {
         Assert.isMainThread();
-        clearBiometricRecognized();
+        clearFingerprintRecognized();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -3010,7 +3021,7 @@
     void handleUserSwitching(int userId, Runnable resultCallback) {
         mLogger.logUserSwitching(userId, "from UserTracker");
         Assert.isMainThread();
-        clearBiometricRecognized();
+        clearFingerprintRecognized();
         boolean trustUsuallyManaged = mTrustManager.isTrustUsuallyManaged(userId);
         mLogger.logTrustUsuallyManagedUpdated(userId, mUserTrustIsUsuallyManaged.get(userId),
                 trustUsuallyManaged, "userSwitching");
@@ -3560,25 +3571,30 @@
         return mServiceStates.get(subId);
     }
 
-    public void clearBiometricRecognized() {
-        clearBiometricRecognized(UserHandle.USER_NULL);
+    /**
+     * Resets the fingerprint authenticated state to false.
+     */
+    public void clearFingerprintRecognized() {
+        clearFingerprintRecognized(UserHandle.USER_NULL);
     }
 
-    public void clearBiometricRecognizedWhenKeyguardDone(int unlockedUser) {
-        clearBiometricRecognized(unlockedUser);
+    /**
+     * Resets the fingerprint authenticated state to false.
+     */
+    public void clearFingerprintRecognizedWhenKeyguardDone(int unlockedUser) {
+        clearFingerprintRecognized(unlockedUser);
     }
 
-    private void clearBiometricRecognized(int unlockedUser) {
+    private void clearFingerprintRecognized(int unlockedUser) {
         Assert.isMainThread();
         mUserFingerprintAuthenticated.clear();
         mTrustManager.clearAllBiometricRecognized(FINGERPRINT, unlockedUser);
-        mTrustManager.clearAllBiometricRecognized(FACE, unlockedUser);
-        mLogger.d("clearBiometricRecognized");
+        mLogger.d("clearFingerprintRecognized");
 
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricsCleared();
+                cb.onFingerprintsCleared();
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 02dd331..9d216dce 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -291,9 +291,14 @@
     public void onLogoutEnabledChanged() { }
 
     /**
-     * Called when authenticated biometrics are cleared.
+     * Called when authenticated fingerprint biometrics are cleared.
      */
-    public void onBiometricsCleared() { }
+    public void onFingerprintsCleared() { }
+
+    /**
+     * Called when authenticated face biometrics have cleared.
+     */
+    public void onFacesCleared() { }
 
     /**
      * Called when the secondary lock screen requirement changes.
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index bd84b28..fda23b7f 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -24,9 +24,9 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockscreenCredential
 import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationResultModel
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -80,13 +80,14 @@
     val isPatternVisible: StateFlow<Boolean>
 
     /**
-     * The current authentication throttling state, set when the user has to wait before being able
-     * to try another authentication attempt. `null` indicates throttling isn't active.
+     * The current authentication lockout (aka "throttling") state, set when the user has to wait
+     * before being able to try another authentication attempt. `null` indicates throttling isn't
+     * active.
      */
-    val throttling: MutableStateFlow<AuthenticationThrottlingModel?>
+    val lockout: MutableStateFlow<AuthenticationLockoutModel?>
 
     /** Whether throttling has occurred at least once since the last successful authentication. */
-    val hasThrottlingOccurred: MutableStateFlow<Boolean>
+    val hasLockoutOccurred: MutableStateFlow<Boolean>
 
     /**
      * Whether the auto confirm feature is enabled for the currently-selected user.
@@ -138,22 +139,25 @@
     /** Reports an authentication attempt. */
     suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)
 
+    /** Reports that the user has entered a temporary device lockout (throttling). */
+    suspend fun reportLockoutStarted(durationMs: Int)
+
     /** Returns the current number of failed authentication attempts. */
     suspend fun getFailedAuthenticationAttemptCount(): Int
 
     /**
-     * Returns the timestamp for when the current throttling will end, allowing the user to attempt
+     * Returns the timestamp for when the current lockout will end, allowing the user to attempt
      * authentication again.
      *
      * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime].
      */
-    suspend fun getThrottlingEndTimestamp(): Long
+    suspend fun getLockoutEndTimestamp(): Long
 
     /**
-     * Sets the throttling timeout duration (time during which the user should not be allowed to
+     * Sets the lockout timeout duration (time during which the user should not be allowed to
      * attempt authentication).
      */
-    suspend fun setThrottleDuration(durationMs: Int)
+    suspend fun setLockoutDuration(durationMs: Int)
 
     /**
      * Checks the given [LockscreenCredential] to see if it's correct, returning an
@@ -185,10 +189,9 @@
             getFreshValue = lockPatternUtils::isVisiblePatternEnabled,
         )
 
-    override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> =
-        MutableStateFlow(null)
+    override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null)
 
-    override val hasThrottlingOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val hasLockoutOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
     override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
         refreshingFlow(
@@ -252,19 +255,25 @@
         }
     }
 
+    override suspend fun reportLockoutStarted(durationMs: Int) {
+        return withContext(backgroundDispatcher) {
+            lockPatternUtils.reportPasswordLockout(durationMs, selectedUserId)
+        }
+    }
+
     override suspend fun getFailedAuthenticationAttemptCount(): Int {
         return withContext(backgroundDispatcher) {
             lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
         }
     }
 
-    override suspend fun getThrottlingEndTimestamp(): Long {
+    override suspend fun getLockoutEndTimestamp(): Long {
         return withContext(backgroundDispatcher) {
             lockPatternUtils.getLockoutAttemptDeadline(selectedUserId)
         }
     }
 
-    override suspend fun setThrottleDuration(durationMs: Int) {
+    override suspend fun setLockoutDuration(durationMs: Int) {
         withContext(backgroundDispatcher) {
             lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs)
         }
@@ -276,9 +285,9 @@
         return withContext(backgroundDispatcher) {
             try {
                 val matched = lockPatternUtils.checkCredential(credential, selectedUserId) {}
-                AuthenticationResultModel(isSuccessful = matched, throttleDurationMs = 0)
+                AuthenticationResultModel(isSuccessful = matched, lockoutDurationMs = 0)
             } catch (ex: LockPatternUtils.RequestThrottledException) {
-                AuthenticationResultModel(isSuccessful = false, throttleDurationMs = ex.timeoutMs)
+                AuthenticationResultModel(isSuccessful = false, lockoutDurationMs = ex.timeoutMs)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 7f8f887..797154e 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -20,9 +20,9 @@
 import com.android.internal.widget.LockPatternView
 import com.android.internal.widget.LockscreenCredential
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -85,10 +85,11 @@
     val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod
 
     /**
-     * The current authentication throttling state, set when the user has to wait before being able
-     * to try another authentication attempt. `null` indicates throttling isn't active.
+     * The current authentication lockout (aka "throttling") state, set when the user has to wait
+     * before being able to try another authentication attempt. `null` indicates lockout isn't
+     * active.
      */
-    val throttling: StateFlow<AuthenticationThrottlingModel?> = repository.throttling
+    val lockout: StateFlow<AuthenticationLockoutModel?> = repository.lockout
 
     /**
      * Whether the auto confirm feature is enabled for the currently-selected user.
@@ -97,12 +98,12 @@
      * [hintedPinLength].
      */
     val isAutoConfirmEnabled: StateFlow<Boolean> =
-        combine(repository.isAutoConfirmFeatureEnabled, repository.hasThrottlingOccurred) {
+        combine(repository.isAutoConfirmFeatureEnabled, repository.hasLockoutOccurred) {
                 featureEnabled,
-                hasThrottlingOccurred ->
-                // Disable auto-confirm if throttling occurred since the last successful
+                hasLockoutOccurred ->
+                // Disable auto-confirm if lockout occurred since the last successful
                 // authentication attempt.
-                featureEnabled && !hasThrottlingOccurred
+                featureEnabled && !hasLockoutOccurred
             }
             .stateIn(
                 scope = applicationScope,
@@ -139,7 +140,7 @@
     /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */
     val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = repository.isPinEnhancedPrivacyEnabled
 
-    private var throttlingCountdownJob: Job? = null
+    private var lockoutCountdownJob: Job? = null
 
     init {
         applicationScope.launch {
@@ -188,8 +189,8 @@
         val authMethod = getAuthenticationMethod()
         val skipCheck =
             when {
-                // Throttling is active, the UI layer should not have called this; skip the attempt.
-                throttling.value != null -> true
+                // Lockout is active, the UI layer should not have called this; skip the attempt.
+                lockout.value != null -> true
                 // The input is too short; skip the attempt.
                 input.isTooShort(authMethod) -> true
                 // Auto-confirm attempt when the feature is not enabled; skip the attempt.
@@ -215,21 +216,22 @@
             )
         }
 
-        // Check if we need to throttle and, if so, kick off the throttle countdown:
-        if (!authenticationResult.isSuccessful && authenticationResult.throttleDurationMs > 0) {
-            repository.setThrottleDuration(
-                durationMs = authenticationResult.throttleDurationMs,
-            )
-            repository.hasThrottlingOccurred.value = true
-            startThrottlingCountdown()
+        // Check if lockout should start and, if so, kick off the countdown:
+        if (!authenticationResult.isSuccessful && authenticationResult.lockoutDurationMs > 0) {
+            repository.apply {
+                setLockoutDuration(durationMs = authenticationResult.lockoutDurationMs)
+                reportLockoutStarted(durationMs = authenticationResult.lockoutDurationMs)
+                hasLockoutOccurred.value = true
+            }
+            startLockoutCountdown()
         }
 
         if (authenticationResult.isSuccessful) {
-            // Since authentication succeeded, we should refresh throttling to make sure that our
-            // state is completely reflecting the upstream source of truth.
-            refreshThrottling()
+            // Since authentication succeeded, refresh lockout to make sure the state is completely
+            // reflecting the upstream source of truth.
+            refreshLockout()
 
-            repository.hasThrottlingOccurred.value = false
+            repository.hasLockoutOccurred.value = false
         }
 
         return if (authenticationResult.isSuccessful) {
@@ -247,52 +249,52 @@
         }
     }
 
-    /** Starts refreshing the throttling state every second. */
-    private suspend fun startThrottlingCountdown() {
-        cancelThrottlingCountdown()
-        throttlingCountdownJob =
+    /** Starts refreshing the lockout state every second. */
+    private suspend fun startLockoutCountdown() {
+        cancelLockoutCountdown()
+        lockoutCountdownJob =
             applicationScope.launch {
-                while (refreshThrottling()) {
+                while (refreshLockout()) {
                     delay(1.seconds.inWholeMilliseconds)
                 }
             }
     }
 
-    /** Cancels any throttling state countdown started in [startThrottlingCountdown]. */
-    private fun cancelThrottlingCountdown() {
-        throttlingCountdownJob?.cancel()
-        throttlingCountdownJob = null
+    /** Cancels any lockout state countdown started in [startLockoutCountdown]. */
+    private fun cancelLockoutCountdown() {
+        lockoutCountdownJob?.cancel()
+        lockoutCountdownJob = null
     }
 
     /** Notifies that the currently-selected user has changed. */
     private suspend fun onSelectedUserChanged() {
-        cancelThrottlingCountdown()
-        if (refreshThrottling()) {
-            startThrottlingCountdown()
+        cancelLockoutCountdown()
+        if (refreshLockout()) {
+            startLockoutCountdown()
         }
     }
 
     /**
-     * Refreshes the throttling state, hydrating the repository with the latest state.
+     * Refreshes the lockout state, hydrating the repository with the latest state.
      *
-     * @return Whether throttling is active or not.
+     * @return Whether lockout is active or not.
      */
-    private suspend fun refreshThrottling(): Boolean {
-        withContext("$TAG#refreshThrottling", backgroundDispatcher) {
+    private suspend fun refreshLockout(): Boolean {
+        withContext("$TAG#refreshLockout", backgroundDispatcher) {
             val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
-            val deadline = async { repository.getThrottlingEndTimestamp() }
+            val deadline = async { repository.getLockoutEndTimestamp() }
             val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
-            repository.throttling.value =
+            repository.lockout.value =
                 if (remainingMs > 0) {
-                    AuthenticationThrottlingModel(
+                    AuthenticationLockoutModel(
                         failedAttemptCount = failedAttemptCount.await(),
                         remainingSeconds = ceil(remainingMs / 1000f).toInt(),
                     )
                 } else {
-                    null // Throttling ended.
+                    null // Lockout ended.
                 }
         }
-        return repository.throttling.value != null
+        return repository.lockout.value != null
     }
 
     private fun AuthenticationMethodModel.createCredential(
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationLockoutModel.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
rename to packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationLockoutModel.kt
index 8392528..8ee2d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationLockoutModel.kt
@@ -16,17 +16,17 @@
 
 package com.android.systemui.authentication.shared.model
 
-/** Models a state for throttling the next authentication attempt. */
-data class AuthenticationThrottlingModel(
+/** Models a state for temporarily locking out the next authentication attempt. */
+data class AuthenticationLockoutModel(
 
-    /** Number of failed authentication attempts so far. If not throttling this will be `0`. */
+    /** Number of failed authentication attempts so far. If not locked out this will be `0`. */
     val failedAttemptCount: Int = 0,
 
     /**
      * Remaining amount of time, in seconds, before another authentication attempt can be done. If
-     * not throttling this will be `0`.
+     * not locked out this will be `0`.
      *
-     * This number is changed throughout the timeout.
+     * This number is changed throughout the lockout.
      *
      * Note: this isn't precise (in milliseconds), but rounded up to ensure "at most" this amount of
      * seconds remains.
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
index f2a3e74..addc75e 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
@@ -21,5 +21,5 @@
     /** Whether authentication was successful. */
     val isSuccessful: Boolean = false,
     /** If [isSuccessful] is `false`, how long the user must wait before trying again. */
-    val throttleDurationMs: Int = 0,
+    val lockoutDurationMs: Int = 0,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index a2ac66f..63fe26a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -197,11 +197,42 @@
                 listenForGoneToAodTransition(this)
                 listenForLockscreenAodTransitions(this)
                 listenForAodToOccludedTransitions(this)
+                listenForAlternateBouncerToAodTransitions(this)
+                listenForDreamingToAodTransitions(this)
             }
         }
     }
 
     @VisibleForTesting
+    suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job {
+        return scope.launch {
+            transitionInteractor.transition(KeyguardState.DREAMING, KeyguardState.AOD).collect {
+                transitionStep ->
+                view.onDozeAmountChanged(
+                    transitionStep.value,
+                    transitionStep.value,
+                    ANIMATE_APPEAR_ON_SCREEN_OFF,
+                )
+            }
+        }
+    }
+
+    @VisibleForTesting
+    suspend fun listenForAlternateBouncerToAodTransitions(scope: CoroutineScope): Job {
+        return scope.launch {
+            transitionInteractor
+                .transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD)
+                .collect { transitionStep ->
+                    view.onDozeAmountChanged(
+                        transitionStep.value,
+                        transitionStep.value,
+                        UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN,
+                    )
+                }
+        }
+    }
+
+    @VisibleForTesting
     suspend fun listenForAodToOccludedTransitions(scope: CoroutineScope): Job {
         return scope.launch {
             transitionInteractor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED).collect {
@@ -246,7 +277,10 @@
     suspend fun listenForLockscreenAodTransitions(scope: CoroutineScope): Job {
         return scope.launch {
             transitionInteractor.dozeAmountTransition.collect { transitionStep ->
-                if (transitionStep.transitionState == TransitionState.CANCELED) {
+                if (
+                    transitionStep.from == KeyguardState.AOD &&
+                        transitionStep.transitionState == TransitionState.CANCELED
+                ) {
                     if (
                         transitionInteractor.startedKeyguardTransitionStep.first().to !=
                             KeyguardState.AOD
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 677f60d..724c0fe 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -19,8 +19,8 @@
 import android.content.Context
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.bouncer.data.repository.BouncerRepository
 import com.android.systemui.classifier.FalsingClassifier
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
@@ -60,24 +60,25 @@
 
     /** The user-facing message to show in the bouncer. */
     val message: StateFlow<String?> =
-        combine(repository.message, authenticationInteractor.throttling) { message, throttling ->
-                messageOrThrottlingMessage(message, throttling)
+        combine(repository.message, authenticationInteractor.lockout) { message, lockout ->
+                messageOrLockoutMessage(message, lockout)
             }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue =
-                    messageOrThrottlingMessage(
+                    messageOrLockoutMessage(
                         repository.message.value,
-                        authenticationInteractor.throttling.value,
+                        authenticationInteractor.lockout.value,
                     )
             )
 
     /**
-     * The current authentication throttling state, set when the user has to wait before being able
-     * to try another authentication attempt. `null` indicates throttling isn't active.
+     * The current authentication lockout (aka "throttling") state, set when the user has to wait
+     * before being able to try another authentication attempt. `null` indicates lockout isn't
+     * active.
      */
-    val throttling: StateFlow<AuthenticationThrottlingModel?> = authenticationInteractor.throttling
+    val lockout: StateFlow<AuthenticationLockoutModel?> = authenticationInteractor.lockout
 
     /** Whether the auto confirm feature is enabled for the currently-selected user. */
     val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled
@@ -102,9 +103,9 @@
 
     init {
         if (flags.isEnabled()) {
-            // Clear the message if moved from throttling to no-longer throttling.
+            // Clear the message if moved from locked-out to no-longer locked-out.
             applicationScope.launch {
-                throttling.pairwise().collect { (previous, current) ->
+                lockout.pairwise().collect { (previous, current) ->
                     if (previous != null && current == null) {
                         clearMessage()
                     }
@@ -213,9 +214,9 @@
      * Shows the error message.
      *
      * Callers should use this instead of [authenticate] when they know ahead of time that an auth
-     * attempt will fail but aren't interested in the other side effects like triggering throttling.
+     * attempt will fail but aren't interested in the other side effects like triggering lockout.
      * For example, if the user entered a pattern that's too short, the system can show the error
-     * message without having the attempt trigger throttling.
+     * message without having the attempt trigger lockout.
      */
     private suspend fun showErrorMessage() {
         repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod()))
@@ -250,15 +251,15 @@
         }
     }
 
-    private fun messageOrThrottlingMessage(
+    private fun messageOrLockoutMessage(
         message: String?,
-        throttlingModel: AuthenticationThrottlingModel?,
+        lockoutModel: AuthenticationLockoutModel?,
     ): String {
         return when {
-            throttlingModel != null ->
+            lockoutModel != null ->
                 applicationContext.getString(
                     com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown,
-                    throttlingModel.remainingSeconds,
+                    lockoutModel.remainingSeconds,
                 )
             message != null -> message
             else -> ""
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
index 5385442..7f97718 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
@@ -21,13 +21,13 @@
 /** Enumerates all known adaptive layout configurations. */
 enum class BouncerSceneLayout {
     /** The default UI with the bouncer laid out normally. */
-    STANDARD,
+    STANDARD_BOUNCER,
     /** The bouncer is displayed vertically stacked with the user switcher. */
-    STACKED,
+    BELOW_USER_SWITCHER,
     /** The bouncer is displayed side-by-side with the user switcher or an empty space. */
-    SIDE_BY_SIDE,
+    BESIDE_USER_SWITCHER,
     /** The bouncer is split in two with both sides shown side-by-side. */
-    SPLIT,
+    SPLIT_BOUNCER,
 }
 
 /** Enumerates the supported window size classes. */
@@ -48,19 +48,19 @@
     isSideBySideSupported: Boolean,
 ): BouncerSceneLayout {
     return when (height) {
-        SizeClass.COMPACT -> BouncerSceneLayout.SPLIT
+        SizeClass.COMPACT -> BouncerSceneLayout.SPLIT_BOUNCER
         SizeClass.MEDIUM ->
             when (width) {
-                SizeClass.COMPACT -> BouncerSceneLayout.STANDARD
-                SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD
-                SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE
+                SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER
+                SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD_BOUNCER
+                SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER
             }
         SizeClass.EXPANDED ->
             when (width) {
-                SizeClass.COMPACT -> BouncerSceneLayout.STANDARD
-                SizeClass.MEDIUM -> BouncerSceneLayout.STACKED
-                SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE
+                SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER
+                SizeClass.MEDIUM -> BouncerSceneLayout.BELOW_USER_SWITCHER
+                SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER
             }
-    }.takeIf { it != BouncerSceneLayout.SIDE_BY_SIDE || isSideBySideSupported }
-        ?: BouncerSceneLayout.STANDARD
+    }.takeIf { it != BouncerSceneLayout.BESIDE_USER_SWITCHER || isSideBySideSupported }
+        ?: BouncerSceneLayout.STANDARD_BOUNCER
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index e379dab..0d7f6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -50,12 +50,12 @@
     abstract val authenticationMethod: AuthenticationMethodModel
 
     /**
-     * String resource ID of the failure message to be shown during throttling.
+     * String resource ID of the failure message to be shown during lockout.
      *
      * The message must include 2 number parameters: the first one indicating how many unsuccessful
-     * attempts were made, and the second one indicating in how many seconds throttling will expire.
+     * attempts were made, and the second one indicating in how many seconds lockout will expire.
      */
-    @get:StringRes abstract val throttlingMessageId: Int
+    @get:StringRes abstract val lockoutMessageId: Int
 
     /** Notifies that the UI has been shown to the user. */
     fun onShown() {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index fefc3e3..4b14343 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -105,17 +105,17 @@
         get() = bouncerInteractor.isUserSwitcherVisible
 
     private val isInputEnabled: StateFlow<Boolean> =
-        bouncerInteractor.throttling
+        bouncerInteractor.lockout
             .map { it == null }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = bouncerInteractor.throttling.value == null,
+                initialValue = bouncerInteractor.lockout.value == null,
             )
 
     // Handle to the scope of the child ViewModel (stored in [authMethod]).
     private var childViewModelScope: CoroutineScope? = null
-    private val _throttlingDialogMessage = MutableStateFlow<String?>(null)
+    private val _dialogMessage = MutableStateFlow<String?>(null)
 
     /** View-model for the current UI, based on the current authentication method. */
     val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> =
@@ -128,20 +128,20 @@
             )
 
     /**
-     * A message for a throttling dialog to show when the user has attempted the wrong credential
-     * too many times and now must wait a while before attempting again.
+     * A message for a dialog to show when the user has attempted the wrong credential too many
+     * times and now must wait a while before attempting again.
      *
      * If `null`, no dialog should be shown.
      *
-     * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user
-     * dismisses this dialog.
+     * Once the dialog is shown, the UI should call [onDialogDismissed] when the user dismisses this
+     * dialog.
      */
-    val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()
+    val dialogMessage: StateFlow<String?> = _dialogMessage.asStateFlow()
 
     /** The user-facing message to show in the bouncer. */
     val message: StateFlow<MessageViewModel> =
-        combine(bouncerInteractor.message, bouncerInteractor.throttling) { message, throttling ->
-                toMessageViewModel(message, isThrottled = throttling != null)
+        combine(bouncerInteractor.message, bouncerInteractor.lockout) { message, lockout ->
+                toMessageViewModel(message, isLockedOut = lockout != null)
             }
             .stateIn(
                 scope = applicationScope,
@@ -149,7 +149,7 @@
                 initialValue =
                     toMessageViewModel(
                         message = bouncerInteractor.message.value,
-                        isThrottled = bouncerInteractor.throttling.value != null,
+                        isLockedOut = bouncerInteractor.lockout.value != null,
                     ),
             )
 
@@ -197,28 +197,28 @@
     init {
         if (flags.isEnabled()) {
             applicationScope.launch {
-                combine(bouncerInteractor.throttling, authMethodViewModel) {
-                        throttling,
+                combine(bouncerInteractor.lockout, authMethodViewModel) {
+                        lockout,
                         authMethodViewModel ->
-                        if (throttling != null && authMethodViewModel != null) {
+                        if (lockout != null && authMethodViewModel != null) {
                             applicationContext.getString(
-                                authMethodViewModel.throttlingMessageId,
-                                throttling.failedAttemptCount,
-                                throttling.remainingSeconds,
+                                authMethodViewModel.lockoutMessageId,
+                                lockout.failedAttemptCount,
+                                lockout.remainingSeconds,
                             )
                         } else {
                             null
                         }
                     }
                     .distinctUntilChanged()
-                    .collect { dialogMessage -> _throttlingDialogMessage.value = dialogMessage }
+                    .collect { dialogMessage -> _dialogMessage.value = dialogMessage }
             }
         }
     }
 
-    /** Notifies that a throttling dialog has been dismissed by the user. */
-    fun onThrottlingDialogDismissed() {
-        _throttlingDialogMessage.value = null
+    /** Notifies that the dialog has been dismissed by the user. */
+    fun onDialogDismissed() {
+        _dialogMessage.value = null
     }
 
     private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean {
@@ -231,11 +231,11 @@
 
     private fun toMessageViewModel(
         message: String?,
-        isThrottled: Boolean,
+        isLockedOut: Boolean,
     ): MessageViewModel {
         return MessageViewModel(
             text = message ?: "",
-            isUpdateAnimated = !isThrottled,
+            isUpdateAnimated = !isLockedOut,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 3b7e321..b682717 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -46,7 +46,7 @@
 
     override val authenticationMethod = AuthenticationMethodModel.Password
 
-    override val throttlingMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
+    override val lockoutMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
 
     /** Whether the input method editor (for example, the software keyboard) is visible. */
     private var isImeVisible: Boolean = false
@@ -56,13 +56,13 @@
 
     /** Whether the UI should request focus on the text field element. */
     val isTextFieldFocusRequested =
-        combine(interactor.throttling, isTextFieldFocused) { throttling, hasFocus ->
+        combine(interactor.lockout, isTextFieldFocused) { throttling, hasFocus ->
                 throttling == null && !hasFocus
             }
             .stateIn(
                 scope = viewModelScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = interactor.throttling.value == null && !isTextFieldFocused.value,
+                initialValue = interactor.lockout.value == null && !isTextFieldFocused.value,
             )
 
     override fun onHidden() {
@@ -104,7 +104,7 @@
      * hidden.
      */
     suspend fun onImeVisibilityChanged(isVisible: Boolean) {
-        if (isImeVisible && !isVisible && interactor.throttling.value == null) {
+        if (isImeVisible && !isVisible && interactor.lockout.value == null) {
             interactor.onImeHiddenByUser()
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index b1c5ab6..69f8032 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -80,7 +80,7 @@
 
     override val authenticationMethod = AuthenticationMethodModel.Pattern
 
-    override val throttlingMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message
+    override val lockoutMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message
 
     /** Notifies that the user has started a drag gesture across the dot grid. */
     fun onDragStart() {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index e25e82f..7f4a029 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -104,7 +104,7 @@
 
     override val authenticationMethod: AuthenticationMethodModel = authenticationMethod
 
-    override val throttlingMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message
+    override val lockoutMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message
 
     init {
         viewModelScope.launch { simBouncerInteractor.subId.collect { onResetSimFlow() } }
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt
index fdd98bec..3063ebd 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt
@@ -18,8 +18,12 @@
 
 /** Models the bounds of the notification container. */
 data class NotificationContainerBounds(
+    /** The position of the left of the container in its window coordinate system, in pixels. */
+    val left: Float = 0f,
     /** The position of the top of the container in its window coordinate system, in pixels. */
     val top: Float = 0f,
+    /** The position of the right of the container in its window coordinate system, in pixels. */
+    val right: Float = 0f,
     /** The position of the bottom of the container in its window coordinate system, in pixels. */
     val bottom: Float = 0f,
     /** Whether any modifications to top/bottom should be smoothly animated. */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 0405ca4..ca8268d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -82,6 +82,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.recordissue.RecordIssueModule;
 import com.android.systemui.retail.dagger.RetailModeModule;
 import com.android.systemui.scene.ui.view.WindowRootViewComponent;
 import com.android.systemui.screenrecord.ScreenRecordModule;
@@ -209,6 +210,7 @@
         PrivacyModule.class,
         QRCodeScannerModule.class,
         QSFragmentStartableModule.class,
+        RecordIssueModule.class,
         ReferenceModule.class,
         RetailModeModule.class,
         ScreenshotModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
index 9c13a8c..3fac865 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
@@ -43,7 +43,7 @@
         if (newState == DozeMachine.State.DOZE || newState == DozeMachine.State.DOZE_AOD) {
             int currentUser = mSelectedUserInteractor.getSelectedUserId();
             if (mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(currentUser)) {
-                mKeyguardUpdateMonitor.clearBiometricRecognized();
+                mKeyguardUpdateMonitor.clearFingerprintRecognized();
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
index 7b33e11..6cb68ba 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
@@ -42,4 +42,6 @@
     @FloatRange(from = 0.0, to = 1.0) val upperBookendScale: Float = 1f,
     /** Vibration scale at the lower bookend of the slider */
     @FloatRange(from = 0.0, to = 1.0) val lowerBookendScale: Float = 0.05f,
+    /** Exponent for power function compensation */
+    @FloatRange(from = 0.0, fromInclusive = false) val exponent: Float = 1f / 0.89f,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
index f313fb3..9e6245a 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
@@ -21,9 +21,11 @@
 import android.view.VelocityTracker
 import android.view.animation.AccelerateInterpolator
 import androidx.annotation.FloatRange
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.statusbar.VibratorHelper
 import kotlin.math.abs
 import kotlin.math.min
+import kotlin.math.pow
 
 /**
  * Listener of slider events that triggers haptic feedback.
@@ -63,18 +65,29 @@
      * @param[absoluteVelocity] Velocity of the handle when it reached the bookend.
      */
     private fun vibrateOnEdgeCollision(absoluteVelocity: Float) {
+        val powerScale = scaleOnEdgeCollision(absoluteVelocity)
+        val vibration =
+            VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, powerScale)
+                .compose()
+        vibratorHelper.vibrate(vibration, VIBRATION_ATTRIBUTES_PIPELINING)
+    }
+
+    /**
+     * Get the velocity-based scale at the bookends
+     *
+     * @param[absoluteVelocity] Velocity of the handle when it reached the bookend.
+     * @return The power scale for the vibration.
+     */
+    @VisibleForTesting
+    fun scaleOnEdgeCollision(absoluteVelocity: Float): Float {
         val velocityInterpolated =
             velocityAccelerateInterpolator.getInterpolation(
                 min(absoluteVelocity / config.maxVelocityToScale, 1f)
             )
         val bookendScaleRange = config.upperBookendScale - config.lowerBookendScale
         val bookendsHitScale = bookendScaleRange * velocityInterpolated + config.lowerBookendScale
-
-        val vibration =
-            VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, bookendsHitScale)
-                .compose()
-        vibratorHelper.vibrate(vibration, VIBRATION_ATTRIBUTES_PIPELINING)
+        return bookendsHitScale.pow(config.exponent)
     }
 
     /**
@@ -96,6 +109,31 @@
         val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress)
         if (deltaProgress < config.deltaProgressForDragThreshold) return
 
+        val powerScale = scaleOnDragTexture(absoluteVelocity, normalizedSliderProgress)
+
+        // Trigger the vibration composition
+        val composition = VibrationEffect.startComposition()
+        repeat(config.numberOfLowTicks) {
+            composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, powerScale)
+        }
+        vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
+        dragTextureLastTime = currentTime
+        dragTextureLastProgress = normalizedSliderProgress
+    }
+
+    /**
+     * Get the scale of the drag texture vibration.
+     *
+     * @param[absoluteVelocity] Absolute velocity of the handle.
+     * @param[normalizedSliderProgress] Progress of the slider handled normalized to the range from
+     *   0F to 1F (inclusive).
+     *     @return the scale of the vibration.
+     */
+    @VisibleForTesting
+    fun scaleOnDragTexture(
+        absoluteVelocity: Float,
+        @FloatRange(from = 0.0, to = 1.0) normalizedSliderProgress: Float
+    ): Float {
         val velocityInterpolated =
             velocityAccelerateInterpolator.getInterpolation(
                 min(absoluteVelocity / config.maxVelocityToScale, 1f)
@@ -113,15 +151,7 @@
 
         // Total scale
         val scale = positionBasedScale + velocityBasedScale
-
-        // Trigger the vibration composition
-        val composition = VibrationEffect.startComposition()
-        repeat(config.numberOfLowTicks) {
-            composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale)
-        }
-        vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
-        dragTextureLastTime = currentTime
-        dragTextureLastProgress = normalizedSliderProgress
+        return scale.pow(config.exponent)
     }
 
     override fun onHandleAcquiredByTouch() {}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 3009087..b7260f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2611,14 +2611,14 @@
         }
 
         if (mGoingToSleep) {
-            mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser);
+            mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser);
             Log.i(TAG, "Device is going to sleep, aborting keyguardDone");
             return;
         }
         setPendingLock(false); // user may have authenticated during the screen off animation
 
         handleHide();
-        mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser);
+        mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser);
         Trace.endSection();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index e47c448..eceaf6c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -326,7 +326,7 @@
                     it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                 },
             )
-            .flowOn(backgroundDispatcher)
+            .flowOn(mainDispatcher) // should revoke auth ASAP in the main thread
             .onEach { anyOfThemIsTrue ->
                 if (anyOfThemIsTrue) {
                     clearPendingAuthRequest("Resetting auth status")
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 0b6b971..7fdcf2f 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
@@ -48,7 +48,7 @@
     override fun start() {
         listenForDreamingToOccluded()
         listenForDreamingToGone()
-        listenForDreamingToDozing()
+        listenForDreamingToAodOrDozing()
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
@@ -94,7 +94,7 @@
         }
     }
 
-    private fun listenForDreamingToDozing() {
+    private fun listenForDreamingToAodOrDozing() {
         scope.launch {
             combine(
                     keyguardInteractor.dozeTransitionModel,
@@ -102,11 +102,12 @@
                     ::Pair
                 )
                 .collect { (dozeTransitionModel, keyguardState) ->
-                    if (
-                        dozeTransitionModel.to == DozeStateModel.DOZE &&
-                            keyguardState == KeyguardState.DREAMING
-                    ) {
-                        startTransitionTo(KeyguardState.DOZING)
+                    if (keyguardState == KeyguardState.DREAMING) {
+                        if (dozeTransitionModel.to == DozeStateModel.DOZE) {
+                            startTransitionTo(KeyguardState.DOZING)
+                        } else if (dozeTransitionModel.to == DozeStateModel.DOZE_AOD) {
+                            startTransitionTo(KeyguardState.AOD)
+                        }
                     }
                 }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
index 5ed70b5..046916a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -78,6 +78,9 @@
  * flows.
  */
 interface FaceAuthenticationListener {
+    /** Receive face isAuthenticated updates */
+    fun onAuthenticatedChanged(isAuthenticated: Boolean)
+
     /** Receive face authentication status updates */
     fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 448411e..7882a97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -66,6 +67,7 @@
 @Inject
 constructor(
     private val keyguardInteractor: KeyguardInteractor,
+    private val shadeInteractor: ShadeInteractor,
     private val lockPatternUtils: LockPatternUtils,
     private val keyguardStateController: KeyguardStateController,
     private val userTracker: UserTracker,
@@ -100,9 +102,10 @@
             quickAffordanceAlwaysVisible(position),
             keyguardInteractor.isDozing,
             keyguardInteractor.isKeyguardShowing,
+            shadeInteractor.anyExpansion,
             biometricSettingsRepository.isCurrentUserInLockdown,
-        ) { affordance, isDozing, isKeyguardShowing, isUserInLockdown ->
-            if (!isDozing && isKeyguardShowing && !isUserInLockdown) {
+        ) { affordance, isDozing, isKeyguardShowing, qsExpansion, isUserInLockdown ->
+            if (!isDozing && isKeyguardShowing && (qsExpansion < 1.0f) && !isUserInLockdown) {
                 affordance
             } else {
                 KeyguardQuickAffordanceModel.Hidden
@@ -117,10 +120,14 @@
      * This is useful for experiences like the lock screen preview mode, where the affordances must
      * always be visible.
      */
-    fun quickAffordanceAlwaysVisible(
+    suspend fun quickAffordanceAlwaysVisible(
         position: KeyguardQuickAffordancePosition,
     ): Flow<KeyguardQuickAffordanceModel> {
-        return quickAffordanceInternal(position)
+        return if (isFeatureDisabledByDevicePolicy()) {
+            flowOf(KeyguardQuickAffordanceModel.Hidden)
+        } else {
+            quickAffordanceInternal(position)
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index 532df4a..fb20000 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.app.trust.TrustManager
 import android.content.Context
 import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.biometrics.BiometricSourceType
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.keyguard.FaceWakeUpTriggersConfig
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -83,6 +85,7 @@
     private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig,
     private val powerInteractor: PowerInteractor,
     private val biometricSettingsRepository: BiometricSettingsRepository,
+    private val trustManager: TrustManager,
 ) : CoreStartable, KeyguardFaceAuthInteractor {
 
     private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -291,6 +294,20 @@
             .onEach { running -> listeners.forEach { it.onRunningStateChanged(running) } }
             .flowOn(mainDispatcher)
             .launchIn(applicationScope)
+        repository.isAuthenticated
+            .sample(userRepository.selectedUserInfo, ::Pair)
+            .onEach { (isAuthenticated, userInfo) ->
+                if (!isAuthenticated) {
+                    faceAuthenticationLogger.clearFaceRecognized()
+                    trustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, userInfo.id)
+                }
+            }
+            .flowOn(backgroundDispatcher)
+            .onEach { (isAuthenticated, _) ->
+                listeners.forEach { it.onAuthenticatedChanged(isAuthenticated) }
+            }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
 
         biometricSettingsRepository.isFaceAuthEnrolledAndEnabled
             .onEach { enrolledAndEnabled ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index a64a422..e7b6e44 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -26,7 +26,6 @@
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
@@ -92,13 +91,7 @@
             connect(R.id.nssl_placeholder, START, PARENT_ID, START)
             connect(R.id.nssl_placeholder, END, PARENT_ID, END)
 
-            val lockId =
-                if (DeviceEntryUdfpsRefactor.isEnabled) {
-                    R.id.device_entry_icon_view
-                } else {
-                    R.id.lock_icon_view
-                }
-            connect(R.id.nssl_placeholder, BOTTOM, lockId, TOP)
+            addNotificationPlaceholderBarrier(this)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
index a25471c..400d0dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
@@ -20,7 +20,12 @@
 import android.content.Context
 import android.view.View
 import android.view.ViewGroup
+import androidx.constraintlayout.widget.Barrier
 import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.res.R
@@ -54,6 +59,29 @@
     private val placeHolderId = R.id.nssl_placeholder
     private var disposableHandle: DisposableHandle? = null
 
+    /**
+     * Align the notification placeholder bottom to the top of either the lock icon or the ambient
+     * indication area, whichever is higher.
+     */
+    protected fun addNotificationPlaceholderBarrier(constraintSet: ConstraintSet) {
+        val lockId =
+            if (DeviceEntryUdfpsRefactor.isEnabled) {
+                R.id.device_entry_icon_view
+            } else {
+                R.id.lock_icon_view
+            }
+
+        constraintSet.apply {
+            createBarrier(
+                R.id.nssl_placeholder_barrier_bottom,
+                Barrier.TOP,
+                0,
+                *intArrayOf(lockId, R.id.ambient_indication_container)
+            )
+            connect(R.id.nssl_placeholder, BOTTOM, R.id.nssl_placeholder_barrier_bottom, TOP)
+        }
+    }
+
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
@@ -85,9 +113,9 @@
             )
         if (sceneContainerFlags.flexiNotifsEnabled()) {
             NotificationStackAppearanceViewBinder.bind(
+                context,
                 sharedNotificationContainer,
                 notificationStackAppearanceViewModel,
-                sceneContainerFlags,
                 ambientState,
                 controller,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
index f5963be..b0b5c81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
@@ -19,14 +19,12 @@
 
 import android.content.Context
 import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
 import androidx.constraintlayout.widget.ConstraintSet.END
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
@@ -97,13 +95,7 @@
             connect(R.id.nssl_placeholder, START, PARENT_ID, START)
             connect(R.id.nssl_placeholder, END, PARENT_ID, END)
 
-            val lockId =
-                if (DeviceEntryUdfpsRefactor.isEnabled) {
-                    R.id.device_entry_icon_view
-                } else {
-                    R.id.lock_icon_view
-                }
-            connect(R.id.nssl_placeholder, BOTTOM, lockId, TOP)
+            addNotificationPlaceholderBarrier(this)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 4588e02..1d4520f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -111,10 +111,21 @@
 
     /** An observable for the alpha level for the entire keyguard root view. */
     val alpha: Flow<Float> =
-        merge(
-            keyguardInteractor.keyguardAlpha.distinctUntilChanged(),
-            occludedToLockscreenTransitionViewModel.lockscreenAlpha,
-        )
+        combine(
+                keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
+                merge(
+                    keyguardInteractor.keyguardAlpha,
+                    occludedToLockscreenTransitionViewModel.lockscreenAlpha,
+                )
+            ) { transitionToGone, alpha ->
+                if (transitionToGone == 1f) {
+                    // Ensures content is not visible when in GONE state
+                    0f
+                } else {
+                    alpha
+                }
+            }
+            .distinctUntilChanged()
 
     private fun burnIn(): Flow<BurnInModel> {
         val dozingAmount: Flow<Float> =
@@ -229,7 +240,9 @@
             .distinctUntilChanged()
 
     fun onNotificationContainerBoundsChanged(top: Float, bottom: Float) {
-        keyguardInteractor.setNotificationContainerBounds(NotificationContainerBounds(top, bottom))
+        keyguardInteractor.setNotificationContainerBounds(
+            NotificationContainerBounds(top = top, bottom = bottom)
+        )
     }
 
     /** Is there an expanded pulse, are we animating in response? */
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 8c5690b..3c2facb 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -132,6 +132,10 @@
         logBuffer.log(TAG, DEBUG, "Face authentication failed")
     }
 
+    fun clearFaceRecognized() {
+        logBuffer.log(TAG, DEBUG, "Clear face recognized")
+    }
+
     fun authenticationError(
         errorCode: Int,
         errString: CharSequence?,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
new file mode 100644
index 0000000..a4088f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.qs.tiles
+
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import android.view.View
+import android.widget.Switch
+import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.Flags.recordIssueQsTile
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class RecordIssueTile
+@Inject
+constructor(
+    host: QSHost,
+    uiEventLogger: QsEventLogger,
+    @Background backgroundLooper: Looper,
+    @Main mainHandler: Handler,
+    falsingManager: FalsingManager,
+    metricsLogger: MetricsLogger,
+    statusBarStateController: StatusBarStateController,
+    activityStarter: ActivityStarter,
+    qsLogger: QSLogger
+) :
+    QSTileImpl<QSTile.BooleanState>(
+        host,
+        uiEventLogger,
+        backgroundLooper,
+        mainHandler,
+        falsingManager,
+        metricsLogger,
+        statusBarStateController,
+        activityStarter,
+        qsLogger
+    ) {
+
+    @VisibleForTesting var isRecording: Boolean = false
+
+    override fun getTileLabel(): CharSequence = mContext.getString(R.string.qs_record_issue_label)
+
+    override fun isAvailable(): Boolean = recordIssueQsTile()
+
+    override fun newTileState(): QSTile.BooleanState =
+        QSTile.BooleanState().apply {
+            label = tileLabel
+            handlesLongClick = false
+        }
+
+    override fun handleClick(view: View?) {
+        isRecording = !isRecording
+        refreshState()
+    }
+
+    override fun getLongClickIntent(): Intent? = null
+
+    @VisibleForTesting
+    public override fun handleUpdateState(qsTileState: QSTile.BooleanState, arg: Any?) {
+        qsTileState.apply {
+            if (isRecording) {
+                value = true
+                state = Tile.STATE_ACTIVE
+                forceExpandIcon = false
+                secondaryLabel = mContext.getString(R.string.qs_record_issue_stop)
+                icon = ResourceIcon.get(R.drawable.qs_record_issue_icon_on)
+            } else {
+                value = false
+                state = Tile.STATE_INACTIVE
+                forceExpandIcon = true
+                secondaryLabel = mContext.getString(R.string.qs_record_issue_start)
+                icon = ResourceIcon.get(R.drawable.qs_record_issue_icon_off)
+            }
+            label = tileLabel
+            contentDescription =
+                if (TextUtils.isEmpty(secondaryLabel)) label else "$label, $secondaryLabel"
+            expandedAccessibilityClassName = Switch::class.java.name
+        }
+    }
+
+    companion object {
+        const val TILE_SPEC = "record_issue"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
new file mode 100644
index 0000000..fc42ba4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.qs.tiles.impl.saver.domain
+
+import android.content.Context
+import android.content.DialogInterface
+import android.content.SharedPreferences
+import android.os.Bundle
+import com.android.internal.R
+import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.DataSaverController
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+class DataSaverDialogDelegate(
+    private val sysuiDialogFactory: SystemUIDialog.Factory,
+    private val context: Context,
+    private val backgroundContext: CoroutineContext,
+    private val dataSaverController: DataSaverController,
+    private val sharedPreferences: SharedPreferences,
+) : SystemUIDialog.Delegate {
+    override fun createDialog(): SystemUIDialog {
+        return sysuiDialogFactory.create(this, context)
+    }
+
+    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+        with(dialog) {
+            setTitle(R.string.data_saver_enable_title)
+            setMessage(R.string.data_saver_description)
+            setPositiveButton(R.string.data_saver_enable_button) { _: DialogInterface?, _ ->
+                CoroutineScope(backgroundContext).launch {
+                    dataSaverController.setDataSaverEnabled(true)
+                }
+
+                sharedPreferences
+                    .edit()
+                    .putBoolean(DataSaverTileUserActionInteractor.DIALOG_SHOWN, true)
+                    .apply()
+            }
+            setNeutralButton(R.string.cancel, null)
+            setShowForAllUsers(true)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
new file mode 100644
index 0000000..25b0913
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.qs.tiles.impl.saver.domain
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [DataSaverTileModel] to [QSTileState]. */
+class DataSaverTileMapper @Inject constructor(@Main private val resources: Resources) :
+    QSTileDataToStateMapper<DataSaverTileModel> {
+    override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState =
+        QSTileState.build(resources, config.uiConfig) {
+            with(data) {
+                if (isEnabled) {
+                    activationState = QSTileState.ActivationState.ACTIVE
+                    icon = { Icon.Resource(R.drawable.qs_data_saver_icon_on, null) }
+                    secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[2]
+                } else {
+                    activationState = QSTileState.ActivationState.INACTIVE
+                    icon = { Icon.Resource(R.drawable.qs_data_saver_icon_off, null) }
+                    secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1]
+                }
+                contentDescription = label
+                supportedActions =
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractor.kt
new file mode 100644
index 0000000..91e049b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractor.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.qs.tiles.impl.saver.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.statusbar.policy.DataSaverController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Observes data saver state changes providing the [DataSaverTileModel]. */
+class DataSaverTileDataInteractor
+@Inject
+constructor(
+    private val dataSaverController: DataSaverController,
+) : QSTileDataInteractor<DataSaverTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<DataSaverTileModel> =
+        ConflatedCallbackFlow.conflatedCallbackFlow {
+            val initialValue = dataSaverController.isDataSaverEnabled
+            trySend(DataSaverTileModel(initialValue))
+
+            val callback = DataSaverController.Listener { trySend(DataSaverTileModel(it)) }
+
+            dataSaverController.addCallback(callback)
+            awaitClose { dataSaverController.removeCallback(callback) }
+        }
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt
new file mode 100644
index 0000000..af74409
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.qs.tiles.impl.saver.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverDialogDelegate
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.DataSaverController
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles data saver tile clicks. */
+class DataSaverTileUserActionInteractor
+@Inject
+constructor(
+    @Application private val context: Context,
+    @Main private val coroutineContext: CoroutineContext,
+    @Background private val backgroundContext: CoroutineContext,
+    private val dataSaverController: DataSaverController,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val systemUIDialogFactory: SystemUIDialog.Factory,
+    userFileManager: UserFileManager,
+) : QSTileUserActionInteractor<DataSaverTileModel> {
+    companion object {
+        private const val INTERACTION_JANK_TAG = "start_data_saver"
+        const val PREFS = "data_saver"
+        const val DIALOG_SHOWN = "data_saver_dialog_shown"
+    }
+
+    val sharedPreferences =
+        userFileManager.getSharedPreferences(PREFS, Context.MODE_PRIVATE, context.userId)
+
+    override suspend fun handleInput(input: QSTileInput<DataSaverTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    val wasEnabled: Boolean = data.isEnabled
+                    if (wasEnabled || sharedPreferences.getBoolean(DIALOG_SHOWN, false)) {
+                        withContext(backgroundContext) {
+                            dataSaverController.setDataSaverEnabled(!wasEnabled)
+                        }
+                        return@with
+                    }
+                    // Show a dialog to confirm first. Dialogs shown by the DialogLaunchAnimator
+                    // must be created and shown on the main thread, so we post it to the UI
+                    // handler
+                    withContext(coroutineContext) {
+                        val dialogContext = action.view?.context ?: context
+                        val dialogDelegate =
+                            DataSaverDialogDelegate(
+                                systemUIDialogFactory,
+                                dialogContext,
+                                backgroundContext,
+                                dataSaverController,
+                                sharedPreferences
+                            )
+                        val dialog = systemUIDialogFactory.create(dialogDelegate, dialogContext)
+
+                        if (action.view != null) {
+                            dialogLaunchAnimator.showFromView(
+                                dialog,
+                                action.view!!,
+                                DialogCuj(
+                                    InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+                                    INTERACTION_JANK_TAG
+                                )
+                            )
+                        } else {
+                            dialog.show()
+                        }
+                    }
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_DATA_SAVER_SETTINGS)
+                    )
+                }
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/model/DataSaverTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/model/DataSaverTileModel.kt
new file mode 100644
index 0000000..040c7bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/model/DataSaverTileModel.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.qs.tiles.impl.saver.domain.model
+
+/**
+ * data saver tile model.
+ *
+ * @param isEnabled is true when the data saver is enabled;
+ */
+@JvmInline value class DataSaverTileModel(val isEnabled: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt
new file mode 100644
index 0000000..d67cf4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.recordissue
+
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.RecordIssueTile
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface RecordIssueModule {
+    /** Inject RecordIssueTile into tileMap in QSModule */
+    @Binds
+    @IntoMap
+    @StringKey(RecordIssueTile.TILE_SPEC)
+    fun bindRecordIssueTile(recordIssueTile: RecordIssueTile): QSTileImpl<*>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index e9779cd..5fbb60d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -59,6 +59,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.ui.view.WindowRootViewComponent;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -116,6 +117,7 @@
     private final AuthController mAuthController;
     private final Lazy<SelectedUserInteractor> mUserInteractor;
     private final Lazy<ShadeInteractor> mShadeInteractorLazy;
+    private final SceneContainerFlags mSceneContainerFlags;
     private ViewGroup mWindowRootView;
     private LayoutParams mLp;
     private boolean mHasTopUi;
@@ -162,7 +164,8 @@
             Lazy<ShadeInteractor> shadeInteractorLazy,
             ShadeWindowLogger logger,
             Lazy<SelectedUserInteractor> userInteractor,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            SceneContainerFlags sceneContainerFlags) {
         mContext = context;
         mWindowRootViewComponentFactory = windowRootViewComponentFactory;
         mWindowManager = windowManager;
@@ -180,6 +183,7 @@
         dumpManager.registerDumpable(this);
         mAuthController = authController;
         mUserInteractor = userInteractor;
+        mSceneContainerFlags = sceneContainerFlags;
         mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
         mLockScreenDisplayTimeout = context.getResources()
                 .getInteger(R.integer.config_lockScreenDisplayTimeout);
@@ -287,6 +291,15 @@
         mLp.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
         mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 
+        if (mSceneContainerFlags.isEnabled()) {
+            // This prevents the appearance and disappearance of the software keyboard (also known
+            // as the "IME") from scrolling/panning the window to make room for the keyboard.
+            //
+            // The scene container logic does its own adjustment and animation when the IME appears
+            // or disappears.
+            mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+        }
+
         mWindowManager.addView(mWindowRootView, mLp);
 
         mLpChanged.copyFrom(mLp);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index a3adea0..642eacc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -35,6 +35,10 @@
 import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
 import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverTileMapper
+import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileDataInteractor
+import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
 import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
@@ -85,6 +89,7 @@
     companion object {
 
         const val AIRPLANE_MODE_TILE_SPEC = "airplane"
+        const val DATA_SAVER_TILE_SPEC = "saver"
 
         /** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */
         @Provides
@@ -132,5 +137,36 @@
                 stateInteractor,
                 mapper,
             )
+
+        @Provides
+        @IntoMap
+        @StringKey(DATA_SAVER_TILE_SPEC)
+        fun provideDataSaverTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(DATA_SAVER_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_data_saver_icon_off,
+                        labelRes = R.string.data_saver,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject DataSaverTile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(DATA_SAVER_TILE_SPEC)
+        fun provideDataSaverTileViewModel(
+            factory: QSTileViewModelFactory.Static<DataSaverTileModel>,
+            mapper: DataSaverTileMapper,
+            stateInteractor: DataSaverTileDataInteractor,
+            userActionInteractor: DataSaverTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(DATA_SAVER_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index abf09ae..e78a694 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -26,5 +26,8 @@
 @SysUISingleton
 class NotificationStackAppearanceRepository @Inject constructor() {
     /** The bounds of the notification stack in the current scene. */
-    val stackBounds = MutableStateFlow(NotificationContainerBounds(0f, 0f))
+    val stackBounds = MutableStateFlow(NotificationContainerBounds())
+
+    /** The corner radius of the notification stack, in dp. */
+    val cornerRadiusDp = MutableStateFlow(32f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 32e4e89..61a4dfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -39,4 +39,7 @@
         check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
         repository.stackBounds.value = bounds
     }
+
+    /** The corner radius of the notification stack, in dp. */
+    val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index fa7a8fd..a9b542d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -16,14 +16,16 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewbinder
 
+import android.content.Context
+import android.util.TypedValue
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
+import kotlin.math.roundToInt
 import kotlinx.coroutines.launch
 
 /** Binds the shared notification container to its view-model. */
@@ -31,9 +33,9 @@
 
     @JvmStatic
     fun bind(
+        context: Context,
         view: SharedNotificationContainer,
         viewModel: NotificationStackAppearanceViewModel,
-        sceneContainerFlags: SceneContainerFlags,
         ambientState: AmbientState,
         controller: NotificationStackScrollLayoutController,
     ) {
@@ -45,6 +47,14 @@
                             bounds.top,
                             controller.isAddOrRemoveAnimationPending
                         )
+                        controller.setRoundedClippingBounds(
+                            it.left,
+                            it.top,
+                            it.right,
+                            it.bottom,
+                            viewModel.cornerRadiusDp.value.dpToPx(context),
+                            viewModel.cornerRadiusDp.value.dpToPx(context),
+                        )
                     }
                 }
                 launch {
@@ -56,4 +66,13 @@
             }
         }
     }
+
+    private fun Float.dpToPx(context: Context): Int {
+        return TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP,
+                this,
+                context.resources.displayMetrics
+            )
+            .roundToInt()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index f4c0e92..834d3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 
 /** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
 @SysUISingleton
@@ -37,4 +38,7 @@
 
     /** The bounds of the notification stack in the current scene. */
     val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
+
+    /** The corner radius of the notification stack, in dp. */
+    val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index c6fd98e..9f22118 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
 import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
 
 /**
  * ViewModel used by the Notification placeholders inside the scene container to update the
@@ -55,9 +56,14 @@
      *   pixels.
      */
     fun onBoundsChanged(
+        left: Float,
         top: Float,
+        right: Float,
         bottom: Float,
     ) {
-        interactor.setStackBounds(NotificationContainerBounds(top, bottom))
+        interactor.setStackBounds(NotificationContainerBounds(left, top, right, bottom))
     }
+
+    /** The corner radius of the placeholder, in dp. */
+    val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 5b854e6..9594bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -177,8 +177,8 @@
             }
             .stateIn(
                 scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = NotificationContainerBounds(0f, 0f),
+                started = SharingStarted.Lazily,
+                initialValue = NotificationContainerBounds(),
             )
 
     val alpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
index 99b123f..ae58398 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
@@ -161,13 +161,13 @@
             if (it == null) {
                 notConnectedFlow
             } else {
-                val secondary = it.contentDescription.toString()
+                val secondary = it.contentDescription
                 flowOf(
                     InternetTileModel.Active(
-                        secondaryTitle = secondary,
+                        secondaryLabel = secondary?.toText(),
                         iconId = it.res,
                         stateDescription = null,
-                        contentDescription = ContentDescription.Loaded(secondary),
+                        contentDescription = secondary,
                     )
                 )
             }
@@ -241,5 +241,11 @@
                 string.substring(1, length - 1)
             } else string
         }
+
+        private fun ContentDescription.toText(): Text =
+            when (this) {
+                is ContentDescription.Loaded -> Text.Loaded(this.description)
+                is ContentDescription.Resource -> Text.Resource(this.res)
+            }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 80c6802..756c440 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -484,7 +484,12 @@
         }
 
         @Override
-        public void onBiometricsCleared() {
+        public void onFingerprintsCleared() {
+            update(false /* alwaysUpdate */);
+        }
+
+        @Override
+        public void onFacesCleared() {
             update(false /* alwaysUpdate */);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
index 395d712..ca95822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
@@ -18,10 +18,10 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SIDE_BY_SIDE
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STACKED
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BELOW_USER_SWITCHER
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BESIDE_USER_SWITCHER
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT_BOUNCER
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD_BOUNCER
 import com.google.common.truth.Truth.assertThat
 import java.util.Locale
 import org.junit.Test
@@ -84,33 +84,33 @@
             listOf(
                     Phone to
                         Expected(
-                            whenNaturallyHeld = STANDARD,
-                            whenUnnaturallyHeld = SPLIT,
+                            whenNaturallyHeld = STANDARD_BOUNCER,
+                            whenUnnaturallyHeld = SPLIT_BOUNCER,
                         ),
                     Tablet to
                         Expected(
-                            whenNaturallyHeld = SIDE_BY_SIDE,
-                            whenUnnaturallyHeld = STACKED,
+                            whenNaturallyHeld = BESIDE_USER_SWITCHER,
+                            whenUnnaturallyHeld = BELOW_USER_SWITCHER,
                         ),
                     Folded to
                         Expected(
-                            whenNaturallyHeld = STANDARD,
-                            whenUnnaturallyHeld = SPLIT,
+                            whenNaturallyHeld = STANDARD_BOUNCER,
+                            whenUnnaturallyHeld = SPLIT_BOUNCER,
                         ),
                     Unfolded to
                         Expected(
-                            whenNaturallyHeld = SIDE_BY_SIDE,
-                            whenUnnaturallyHeld = STANDARD,
+                            whenNaturallyHeld = BESIDE_USER_SWITCHER,
+                            whenUnnaturallyHeld = STANDARD_BOUNCER,
                         ),
                     TallerFolded to
                         Expected(
-                            whenNaturallyHeld = STANDARD,
-                            whenUnnaturallyHeld = SPLIT,
+                            whenNaturallyHeld = STANDARD_BOUNCER,
+                            whenUnnaturallyHeld = SPLIT_BOUNCER,
                         ),
                     TallerUnfolded to
                         Expected(
-                            whenNaturallyHeld = SIDE_BY_SIDE,
-                            whenUnnaturallyHeld = SIDE_BY_SIDE,
+                            whenNaturallyHeld = BESIDE_USER_SWITCHER,
+                            whenUnnaturallyHeld = BESIDE_USER_SWITCHER,
                         ),
                 )
                 .flatMap { (device, expected) ->
@@ -124,13 +124,13 @@
                             )
                         )
 
-                        if (expected.whenNaturallyHeld == SIDE_BY_SIDE) {
+                        if (expected.whenNaturallyHeld == BESIDE_USER_SWITCHER) {
                             add(
                                 TestCase(
                                     device = device,
                                     held = device.naturallyHeld,
                                     isSideBySideSupported = false,
-                                    expected = STANDARD,
+                                    expected = STANDARD_BOUNCER,
                                 )
                             )
                         }
@@ -144,13 +144,13 @@
                             )
                         )
 
-                        if (expected.whenUnnaturallyHeld == SIDE_BY_SIDE) {
+                        if (expected.whenUnnaturallyHeld == BESIDE_USER_SWITCHER) {
                             add(
                                 TestCase(
                                     device = device,
                                     held = device.naturallyHeld.flip(),
                                     isSideBySideSupported = false,
-                                    expected = STANDARD,
+                                    expected = STANDARD_BOUNCER,
                                 )
                             )
                         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index 7750d25..ab6bc2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
@@ -71,7 +71,7 @@
             VibrationEffect.startComposition()
                 .addPrimitive(
                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    scaleAtBookends(config.maxVelocityToScale)
+                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                 )
                 .compose()
 
@@ -86,7 +86,7 @@
             VibrationEffect.startComposition()
                 .addPrimitive(
                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    scaleAtBookends(config.maxVelocityToScale)
+                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale)
                 )
                 .compose()
 
@@ -102,7 +102,7 @@
             VibrationEffect.startComposition()
                 .addPrimitive(
                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    scaleAtBookends(config.maxVelocityToScale)
+                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                 )
                 .compose()
 
@@ -117,7 +117,7 @@
             VibrationEffect.startComposition()
                 .addPrimitive(
                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    scaleAtBookends(config.maxVelocityToScale)
+                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                 )
                 .compose()
 
@@ -132,7 +132,11 @@
     fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() {
         // GIVEN max velocity and slider progress
         val progress = 1f
-        val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
+        val expectedScale =
+            sliderHapticFeedbackProvider.scaleOnDragTexture(
+                config.maxVelocityToScale,
+                progress,
+            )
         val ticks = VibrationEffect.startComposition()
         repeat(config.numberOfLowTicks) {
             ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
@@ -203,7 +207,11 @@
     fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() {
         // GIVEN max velocity and slider progress
         val progress = 1f
-        val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
+        val expectedScale =
+            sliderHapticFeedbackProvider.scaleOnDragTexture(
+                config.maxVelocityToScale,
+                progress,
+            )
         val ticks = VibrationEffect.startComposition()
         repeat(config.numberOfLowTicks) {
             ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
@@ -212,7 +220,7 @@
             VibrationEffect.startComposition()
                 .addPrimitive(
                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    scaleAtBookends(config.maxVelocityToScale)
+                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                 )
                 .compose()
 
@@ -232,7 +240,11 @@
     fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() {
         // GIVEN max velocity and slider progress
         val progress = 1f
-        val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
+        val expectedScale =
+            sliderHapticFeedbackProvider.scaleOnDragTexture(
+                config.maxVelocityToScale,
+                progress,
+            )
         val ticks = VibrationEffect.startComposition()
         repeat(config.numberOfLowTicks) {
             ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
@@ -241,7 +253,7 @@
             VibrationEffect.startComposition()
                 .addPrimitive(
                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    scaleAtBookends(config.maxVelocityToScale)
+                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                 )
                 .compose()
 
@@ -289,28 +301,12 @@
         assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
     }
 
-    private fun scaleAtBookends(velocity: Float): Float {
-        val range = config.upperBookendScale - config.lowerBookendScale
-        val interpolatedVelocity =
-            velocityInterpolator.getInterpolation(velocity / config.maxVelocityToScale)
-        return interpolatedVelocity * range + config.lowerBookendScale
-    }
-
-    private fun scaleAtProgressChange(velocity: Float, progress: Float): Float {
-        val range = config.progressBasedDragMaxScale - config.progressBasedDragMinScale
-        val interpolatedVelocity =
-            velocityInterpolator.getInterpolation(velocity / config.maxVelocityToScale)
-        val interpolatedProgress = progressInterpolator.getInterpolation(progress)
-        val bump = interpolatedVelocity * config.additionalVelocityMaxBump
-        return interpolatedProgress * range + config.progressBasedDragMinScale + bump
-    }
-
     private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect {
         val ticks = VibrationEffect.startComposition()
         repeat(config.numberOfLowTicks) {
             ticks.addPrimitive(
                 VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
-                scaleAtProgressChange(velocity, progress)
+                sliderHapticFeedbackProvider.scaleOnDragTexture(velocity, progress),
             )
         }
         return ticks.compose()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 40c9432..076d725 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -53,9 +53,11 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -101,6 +103,8 @@
     private lateinit var underTest: CustomizationProvider
     private lateinit var testScope: TestScope
 
+    private val kosmos = testKosmos()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -185,6 +189,7 @@
                                 },
                         )
                         .keyguardInteractor,
+                shadeInteractor = kosmos.shadeInteractor,
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index d246f0e..ae5f625 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -97,6 +97,7 @@
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.scene.FakeWindowRootViewComponent;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.ui.view.WindowRootView;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -214,6 +215,7 @@
     private @Mock CoroutineDispatcher mDispatcher;
     private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
     private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
+    private @Mock SceneContainerFlags mSceneContainerFlags;
 
     private FakeFeatureFlags mFeatureFlags;
     private final int mDefaultUserId = 100;
@@ -258,7 +260,8 @@
                 () -> mShadeInteractor,
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
-                mUserTracker);
+                mUserTracker,
+                mSceneContainerFlags);
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
         mFeatureFlags.set(Flags.REFACTOR_GETCURRENTUSER, true);
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 de12b8f..4ab8e28 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
@@ -17,8 +17,10 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.app.trust.TrustManager
 import android.content.pm.UserInfo
 import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.biometrics.BiometricSourceType
 import android.os.Handler
 import android.os.PowerManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -62,6 +64,7 @@
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -74,8 +77,11 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -99,7 +105,8 @@
 
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig
-    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var trustManager: TrustManager
 
     @Before
     fun setup() {
@@ -146,7 +153,7 @@
                         keyguardUpdateMonitor,
                         FakeTrustRepository(),
                         testScope.backgroundScope,
-                        mSelectedUserInteractor,
+                        selectedUserInteractor,
                         underTest,
                     )
                 },
@@ -169,6 +176,7 @@
                 faceWakeUpTriggersConfig,
                 powerInteractor,
                 fakeBiometricSettingsRepository,
+                trustManager,
             )
     }
 
@@ -498,6 +506,22 @@
             assertThat(faceAuthRepository.isLockedOut.value).isTrue()
         }
 
+    @Test
+    fun whenIsAuthenticatedFalse_clearFaceBiometrics() =
+        testScope.runTest {
+            underTest.start()
+
+            faceAuthRepository.isAuthenticated.value = true
+            runCurrent()
+            verify(trustManager, never())
+                .clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt())
+
+            faceAuthRepository.isAuthenticated.value = false
+            runCurrent()
+
+            verify(trustManager).clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt())
+        }
+
     companion object {
         private const val primaryUserId = 1
         private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 66c8a22..b4ae7e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -46,7 +46,9 @@
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -242,6 +244,8 @@
     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
     private lateinit var userTracker: UserTracker
 
+    private val kosmos = testKosmos()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -311,6 +315,7 @@
                             featureFlags = featureFlags,
                         )
                         .keyguardInteractor,
+                shadeInteractor = kosmos.shadeInteractor,
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 976dc5f..b8a8bdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1137,7 +1137,7 @@
             runCurrent()
 
             // WHEN primary bouncer shows
-            bouncerRepository.setPrimaryShow(true) // beverlyt
+            bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
             val info =
@@ -1232,6 +1232,36 @@
         }
 
     @Test
+    fun dreamingToAod() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DREAMING
+            keyguardRepository.setDreaming(true)
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
+            runCurrent()
+
+            // WHEN the device starts DOZE_AOD
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(
+                    from = DozeStateModel.INITIALIZED,
+                    to = DozeStateModel.DOZE_AOD,
+                )
+            )
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to AOD should occur
+            assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
+            assertThat(info.to).isEqualTo(KeyguardState.AOD)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun lockscreenToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 1584be0..af38523c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -54,9 +54,11 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -108,6 +110,8 @@
     private lateinit var dockManager: DockManagerFake
     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
 
+    private val kosmos = testKosmos()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -221,6 +225,7 @@
                 quickAffordanceInteractor =
                     KeyguardQuickAffordanceInteractor(
                         keyguardInteractor = keyguardInteractor,
+                        shadeInteractor = kosmos.shadeInteractor,
                         lockPatternUtils = lockPatternUtils,
                         keyguardStateController = keyguardStateController,
                         userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 0c30d10..b6a661b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -264,12 +264,14 @@
         whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha)
             .thenReturn(emptyFlow())
         whenever(shadeInteractor.qsExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
+        whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f))
 
         underTest =
             KeyguardQuickAffordancesCombinedViewModel(
                 quickAffordanceInteractor =
                     KeyguardQuickAffordanceInteractor(
                         keyguardInteractor = keyguardInteractor,
+                        shadeInteractor = shadeInteractor,
                         lockPatternUtils = lockPatternUtils,
                         keyguardStateController = keyguardStateController,
                         userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 58624d3..6878007 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -144,19 +144,43 @@
     @Test
     fun alpha() =
         testScope.runTest {
-            val value = collectLastValue(underTest.alpha)
-            assertThat(value()).isEqualTo(0f)
+            val alpha by collectLastValue(underTest.alpha)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.OFF,
+                to = KeyguardState.LOCKSCREEN,
+                testScope = testScope,
+            )
 
             repository.setKeyguardAlpha(0.1f)
-            assertThat(value()).isEqualTo(0.1f)
+            assertThat(alpha).isEqualTo(0.1f)
             repository.setKeyguardAlpha(0.5f)
-            assertThat(value()).isEqualTo(0.5f)
+            assertThat(alpha).isEqualTo(0.5f)
             repository.setKeyguardAlpha(0.2f)
-            assertThat(value()).isEqualTo(0.2f)
+            assertThat(alpha).isEqualTo(0.2f)
             repository.setKeyguardAlpha(0f)
-            assertThat(value()).isEqualTo(0f)
+            assertThat(alpha).isEqualTo(0f)
             occludedToLockscreenAlpha.value = 0.8f
-            assertThat(value()).isEqualTo(0.8f)
+            assertThat(alpha).isEqualTo(0.8f)
+        }
+
+    @Test
+    fun alphaWhenGoneEqualsZero() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope = testScope,
+            )
+
+            repository.setKeyguardAlpha(0.1f)
+            assertThat(alpha).isEqualTo(0f)
+            repository.setKeyguardAlpha(0.5f)
+            assertThat(alpha).isEqualTo(0f)
+            repository.setKeyguardAlpha(1f)
+            assertThat(alpha).isEqualTo(0f)
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
new file mode 100644
index 0000000..d8199c5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.qs.tiles
+
+import android.os.Handler
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/**
+ * This class tests the functionality of the RecordIssueTile. The initial state of the tile is
+ * always be inactive at the start of these tests.
+ */
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class RecordIssueTileTest : SysuiTestCase() {
+
+    @Mock private lateinit var host: QSHost
+    @Mock private lateinit var qsEventLogger: QsEventLogger
+    @Mock private lateinit var metricsLogger: MetricsLogger
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var qsLogger: QSLogger
+
+    private lateinit var tile: RecordIssueTile
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(host.context).thenReturn(mContext)
+
+        val testableLooper = TestableLooper.get(this)
+        tile =
+            RecordIssueTile(
+                host,
+                qsEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                FalsingManagerFake(),
+                metricsLogger,
+                statusBarStateController,
+                activityStarter,
+                qsLogger
+            )
+    }
+
+    @Test
+    fun qsTileUi_shouldLookCorrect_whenInactive() {
+        tile.isRecording = false
+
+        val testState = tile.newTileState()
+        tile.handleUpdateState(testState, null)
+
+        assertThat(testState.state).isEqualTo(Tile.STATE_INACTIVE)
+        assertThat(testState.secondaryLabel.toString())
+            .isEqualTo(mContext.getString(R.string.qs_record_issue_start))
+    }
+
+    @Test
+    fun qsTileUi_shouldLookCorrect_whenRecording() {
+        tile.isRecording = true
+
+        val testState = tile.newTileState()
+        tile.handleUpdateState(testState, null)
+
+        assertThat(testState.state).isEqualTo(Tile.STATE_ACTIVE)
+        assertThat(testState.secondaryLabel.toString())
+            .isEqualTo(mContext.getString(R.string.qs_record_issue_stop))
+    }
+
+    @Test
+    fun inActiveQsTile_switchesToActive_whenClicked() {
+        tile.isRecording = false
+
+        val testState = tile.newTileState()
+        tile.handleUpdateState(testState, null)
+
+        assertThat(testState.state).isEqualTo(Tile.STATE_INACTIVE)
+    }
+
+    @Test
+    fun activeQsTile_switchesToInActive_whenClicked() {
+        tile.isRecording = true
+
+        val testState = tile.newTileState()
+        tile.handleUpdateState(testState, null)
+
+        assertThat(testState.state).isEqualTo(Tile.STATE_ACTIVE)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 39739e7..5ffbe65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -75,6 +75,7 @@
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.shared.logger.SceneLogger;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
@@ -138,6 +139,7 @@
     @Mock private ShadeWindowLogger mShadeWindowLogger;
     @Mock private SelectedUserInteractor mSelectedUserInteractor;
     @Mock private UserTracker mUserTracker;
+    @Mock private SceneContainerFlags mSceneContainerFlags;
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
 
@@ -274,7 +276,8 @@
                 () -> mShadeInteractor,
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
-                mUserTracker) {
+                mUserTracker,
+                mSceneContainerFlags) {
                     @Override
                     protected boolean isDebuggable() {
                         return false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 85b9392..f0205b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -413,7 +413,7 @@
             val top = 123f
             val bottom = 456f
             keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
-            assertThat(bounds).isEqualTo(NotificationContainerBounds(top, bottom))
+            assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom))
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
index 22dce3a..1bdf644 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.Text.Companion.loadText
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
@@ -355,14 +356,14 @@
 
             connectivityRepository.setEthernetConnected(default = true, validated = true)
 
-            assertThat(latest?.secondaryLabel).isNull()
-            assertThat(latest?.secondaryTitle)
-                .isEqualTo(ethernetIcon!!.contentDescription.toString())
+            assertThat(latest?.secondaryLabel.loadText(context))
+                .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+            assertThat(latest?.secondaryTitle).isNull()
             assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully)
             assertThat(latest?.icon).isNull()
             assertThat(latest?.stateDescription).isNull()
             assertThat(latest?.contentDescription.loadContentDescription(context))
-                .isEqualTo(latest?.secondaryTitle)
+                .isEqualTo(latest?.secondaryLabel.loadText(context))
         }
 
     @Test
@@ -373,14 +374,14 @@
 
             connectivityRepository.setEthernetConnected(default = true, validated = false)
 
-            assertThat(latest?.secondaryLabel).isNull()
-            assertThat(latest?.secondaryTitle)
-                .isEqualTo(ethernetIcon!!.contentDescription.toString())
+            assertThat(latest?.secondaryLabel.loadText(context))
+                .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+            assertThat(latest?.secondaryTitle).isNull()
             assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet)
             assertThat(latest?.icon).isNull()
             assertThat(latest?.stateDescription).isNull()
             assertThat(latest?.contentDescription.loadContentDescription(context))
-                .isEqualTo(latest?.secondaryTitle)
+                .isEqualTo(latest?.secondaryLabel.loadText(context))
         }
 
     private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index 01dad38..479309c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -153,6 +153,36 @@
     }
 
     @Test
+    public void testCanSkipLockScreen_updateCalledOnFacesCleared() {
+        verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
+
+        // Cannot skip after there's a password/pin/pattern
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+        ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse();
+
+        // Unless user is authenticated
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true);
+        mUpdateCallbackCaptor.getValue().onFacesCleared();
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue();
+    }
+
+    @Test
+    public void testCanSkipLockScreen_updateCalledOnFingerprintssCleared() {
+        verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
+
+        // Cannot skip after there's a password/pin/pattern
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+        ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse();
+
+        // Unless user is authenticated
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true);
+        mUpdateCallbackCaptor.getValue().onFingerprintsCleared();
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue();
+    }
+
+    @Test
     public void testIsUnlocked() {
         // Is unlocked whenever the keyguard is not showing
         assertThat(mKeyguardStateController.isShowing()).isFalse();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 8585d46..5b9b390 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -119,6 +119,7 @@
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.shared.logger.SceneLogger;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
@@ -343,6 +344,8 @@
     private Icon mAppBubbleIcon;
     @Mock
     private Display mDefaultDisplay;
+    @Mock
+    private SceneContainerFlags mSceneContainerFlags;
 
     private final SceneTestUtils mUtils = new SceneTestUtils(this);
     private final TestScope mTestScope = mUtils.getTestScope();
@@ -503,7 +506,8 @@
                 () -> mShadeInteractor,
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
-                mUserTracker
+                mUserTracker,
+                mSceneContainerFlags
         );
         mNotificationShadeWindowController.fetchWindowRootView();
         mNotificationShadeWindowController.attach();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index 4642b47..7c5696c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -20,10 +20,10 @@
 import com.android.internal.widget.LockPatternView
 import com.android.internal.widget.LockscreenCredential
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationResultModel
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.dagger.SysUISingleton
 import dagger.Binds
 import dagger.Module
@@ -47,10 +47,9 @@
     private val _isPatternVisible = MutableStateFlow(true)
     override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow()
 
-    override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> =
-        MutableStateFlow(null)
+    override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null)
 
-    override val hasThrottlingOccurred = MutableStateFlow(false)
+    override val hasLockoutOccurred = MutableStateFlow(false)
 
     private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false)
     override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
@@ -70,10 +69,12 @@
         _isPinEnhancedPrivacyEnabled.asStateFlow()
 
     private var failedAttemptCount = 0
-    private var throttlingEndTimestamp = 0L
+    private var lockoutEndTimestamp = 0L
     private var credentialOverride: List<Any>? = null
     private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
 
+    var lockoutStartedReportCount = 0
+
     override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
         return authenticationMethod.value
     }
@@ -92,6 +93,10 @@
         authenticationChallengeResult.emit(isSuccessful)
     }
 
+    override suspend fun reportLockoutStarted(durationMs: Int) {
+        lockoutStartedReportCount++
+    }
+
     override suspend fun getPinLength(): Int {
         return (credentialOverride ?: DEFAULT_PIN).size
     }
@@ -100,18 +105,18 @@
         return failedAttemptCount
     }
 
-    override suspend fun getThrottlingEndTimestamp(): Long {
-        return throttlingEndTimestamp
+    override suspend fun getLockoutEndTimestamp(): Long {
+        return lockoutEndTimestamp
     }
 
     fun setAutoConfirmFeatureEnabled(isEnabled: Boolean) {
         _isAutoConfirmFeatureEnabled.value = isEnabled
     }
 
-    override suspend fun setThrottleDuration(durationMs: Int) {
-        throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
+    override suspend fun setLockoutDuration(durationMs: Int) {
+        lockoutEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
         if (durationMs > 0) {
-            hasThrottlingOccurred.value = true
+            hasLockoutOccurred.value = true
         }
     }
 
@@ -131,18 +136,16 @@
                 else -> error("Unexpected credential type ${credential.type}!")
             }
 
-        return if (
-            isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
-        ) {
-            hasThrottlingOccurred.value = false
+        return if (isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
+            hasLockoutOccurred.value = false
             AuthenticationResultModel(
                 isSuccessful = isSuccessful,
-                throttleDurationMs = 0,
+                lockoutDurationMs = 0,
             )
         } else {
             AuthenticationResultModel(
                 isSuccessful = false,
-                throttleDurationMs = THROTTLE_DURATION_MS,
+                lockoutDurationMs = LOCKOUT_DURATION_MS,
             )
         }
     }
@@ -172,9 +175,9 @@
                 AuthenticationPatternCoordinate(0, 1),
                 AuthenticationPatternCoordinate(0, 2),
             )
-        const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5
-        const val THROTTLE_DURATION_SECONDS = 30
-        const val THROTTLE_DURATION_MS = THROTTLE_DURATION_SECONDS * 1000
+        const val MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT = 5
+        const val LOCKOUT_DURATION_SECONDS = 30
+        const val LOCKOUT_DURATION_MS = LOCKOUT_DURATION_SECONDS * 1000
         const val HINTING_PIN_LENGTH = 6
         val DEFAULT_PIN = buildList { repeat(HINTING_PIN_LENGTH) { add(it + 1) } }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index e289083..a1b6587 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -31,7 +31,6 @@
 
 @SysUISingleton
 class FakeDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceAuthRepository {
-
     override val isAuthenticated = MutableStateFlow(false)
     override val canRunFaceAuth = MutableStateFlow(false)
     private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.kt
new file mode 100644
index 0000000..e9a394a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.qs.tiles.impl.saver
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.connectivity.ConnectivityModule
+
+val Kosmos.qsDataSaverTileConfig by
+    Kosmos.Fixture { ConnectivityModule.provideDataSaverTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
index 886722e..bade848 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
@@ -19,19 +19,38 @@
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.DataSaverController.Listener;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class FakeDataSaverController extends BaseLeakChecker<Listener> implements DataSaverController {
 
+    private boolean mIsEnabled = false;
+    private List<Listener> mListeners = new ArrayList<>();
+
     public FakeDataSaverController(LeakCheck test) {
         super(test, "datasaver");
     }
 
     @Override
     public boolean isDataSaverEnabled() {
-        return false;
+        return mIsEnabled;
     }
 
     @Override
     public void setDataSaverEnabled(boolean enabled) {
+        mIsEnabled = enabled;
+        for (Listener listener: mListeners) {
+            listener.onDataSaverChanged(enabled);
+        }
+    }
 
+    @Override
+    public void addCallback(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public void removeCallback(Listener listener) {
+        mListeners.remove(listener);
     }
 }
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index e3f1932..2ba5f6b 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -1,6 +1,8 @@
 # Only classes listed here can use the Ravenwood annotations.
 
 com.android.internal.util.ArrayUtils
+com.android.internal.os.LongArrayMultiStateCounter
+com.android.internal.os.LongArrayMultiStateCounter$LongArrayContainer
 
 android.util.AtomicFile
 android.util.DataUnit
@@ -22,6 +24,7 @@
 android.util.TimeUtils
 android.util.Xml
 
+android.os.BatteryConsumer
 android.os.Binder
 android.os.Binder$IdentitySupplier
 android.os.FileUtils
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index e9bb763..b8cf13b 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -32,6 +32,22 @@
     ],
 }
 
+java_library_static {
+    name: "AccessibilityGestureUtils",
+    srcs: [
+        "java/**/gestures/GestureMatcher.java",
+        "java/**/gestures/GestureManifold.java",
+        "java/**/gestures/MultiFingerMultiTap.java",
+        "java/**/gestures/TouchState.java",
+    ],
+    static_libs: [
+        "services.accessibility",
+    ],
+    libs: [
+        "androidx.annotation_annotation",
+    ],
+}
+
 aconfig_declarations {
     name: "com_android_server_accessibility_flags",
     package: "com.android.server.accessibility",
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index 903a071..e54f0c1 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -82,7 +82,7 @@
  * detector. Gesture matchers are tied to a single gesture. It calls listener callback functions
  * when a gesture starts or completes.
  */
-class GestureManifold implements GestureMatcher.StateChangeListener {
+public class GestureManifold implements GestureMatcher.StateChangeListener {
 
     private static final String LOG_TAG = "GestureManifold";
 
@@ -111,7 +111,7 @@
     // Shared state information.
     private TouchState mState;
 
-    GestureManifold(Context context, Listener listener, TouchState state, Handler handler) {
+    public GestureManifold(Context context, Listener listener, TouchState state, Handler handler) {
         mContext = context;
         mHandler = handler;
         mListener = listener;
@@ -222,7 +222,7 @@
      * @return True if the event has been appropriately handled by the gesture manifold and related
      *     callback functions, false if it should be handled further by the calling function.
      */
-    boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+    public boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         if (mState.isClear()) {
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                 // Validity safeguard: if touch state is clear, then matchers should always be clear
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 0a2a780..baae1d93 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -861,6 +861,7 @@
     }
 
     final class DetectingStateWithMultiFinger extends DetectingState {
+        private static final int TWO_FINGER_GESTURE_MAX_TAPS = 2;
         // A flag set to true when two fingers have touched down.
         // Used to indicate what next finger action should be.
         private boolean mIsTwoFingerCountReached = false;
@@ -917,7 +918,8 @@
                     mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
 
                     if (event.getPointerCount() == 2) {
-                        if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)) {
+                        if (isMultiFingerMultiTapTriggered(
+                                TWO_FINGER_GESTURE_MAX_TAPS - 1, event)) {
                             // 3tap and hold
                             afterLongTapTimeoutTransitionToDraggingState(event);
                         } else {
@@ -962,7 +964,8 @@
                         // (which is a rare combo to be used aside from magnification)
                         if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) {
                             transitionToViewportDraggingStateAndClear(event);
-                        } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)
+                        } else if (isMultiFingerMultiTapTriggered(
+                                TWO_FINGER_GESTURE_MAX_TAPS - 1, event)
                                 && event.getPointerCount() == 2) {
                             transitionToViewportDraggingStateAndClear(event);
                         } else if (isActivated() && event.getPointerCount() == 2) {
@@ -1009,7 +1012,7 @@
                             mDisplayId, event.getX(), event.getY())) {
                         transitionToDelegatingStateAndClear();
 
-                    } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 3, event)) {
+                    } else if (isMultiFingerMultiTapTriggered(TWO_FINGER_GESTURE_MAX_TAPS, event)) {
                         // Placing multiple fingers before a single finger, because achieving a
                         // multi finger multi tap also means achieving a single finger triple tap
                         onTripleTap(event);
@@ -1051,7 +1054,7 @@
                 mIsTwoFingerCountReached = false;
             }
 
-            if (mDetectTwoFingerTripleTap && mCompletedTapCount > 2) {
+            if (mDetectTwoFingerTripleTap && mCompletedTapCount > TWO_FINGER_GESTURE_MAX_TAPS - 1) {
                 final boolean enabled = !isActivated();
                 mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
             }
@@ -1075,7 +1078,7 @@
             // Only log the 3tap and hold event
             if (!shortcutTriggered) {
                 final boolean enabled = !isActivated();
-                if (mCompletedTapCount == 2) {
+                if (mCompletedTapCount == TWO_FINGER_GESTURE_MAX_TAPS - 1) {
                     // Two finger triple tap and hold
                     mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
                 } else {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 73c267a..75d01f5 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -481,10 +481,10 @@
 
                 if (mDetectTwoFingerTripleTap) {
                     mGestureMatchers.add(new MultiFingerMultiTap(context, /* fingers= */ 2,
-                            /* taps= */ 3, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP,
+                            /* taps= */ 2, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP,
                             null));
                     mGestureMatchers.add(new MultiFingerMultiTapAndHold(context, /* fingers= */ 2,
-                            /* taps= */ 3, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD,
+                            /* taps= */ 2, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD,
                             null));
                 }
 
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index c111ec3..1f89e57b 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -253,13 +253,8 @@
         mInputManagerInternal.setDisplayEligibilityForPointerCapture(displayId, isEligible);
     }
 
-    void setLocalIme(int displayId) {
-        // WM throws a SecurityException if the display is untrusted.
-        if ((mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
-                == Display.FLAG_TRUSTED) {
-            mWindowManager.setDisplayImePolicy(displayId,
-                    WindowManager.DISPLAY_IME_POLICY_LOCAL);
-        }
+    void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
+        mWindowManager.setDisplayImePolicy(displayId, policy);
     }
 
     @GuardedBy("mLock")
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 13c7924..58aa2c3 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -899,6 +899,24 @@
 
     @Override // Binder call
     @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
+        super.setDisplayImePolicy_enforcePermission();
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplays.contains(displayId)) {
+                throw new SecurityException("Display ID " + displayId
+                        + " not found for this virtual device");
+            }
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mInputController.setDisplayImePolicy(displayId, policy);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @Nullable
     public List<VirtualSensor> getVirtualSensorList() {
         super.getVirtualSensorList_enforcePermission();
@@ -1095,7 +1113,12 @@
             mInputController.setPointerAcceleration(1f, displayId);
             mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
                     displayId);
-            mInputController.setLocalIme(displayId);
+            // WM throws a SecurityException if the display is untrusted.
+            if ((mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
+                    == Display.FLAG_TRUSTED) {
+                mInputController.setDisplayImePolicy(displayId,
+                        WindowManager.DISPLAY_IME_POLICY_LOCAL);
+            }
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index b9a3c0c4..33726d1 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import static android.os.Flags.stateOfHealthPublic;
+import static android.os.Flags.batteryServiceSupportCurrentAdbCommand;
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import static com.android.server.health.Utils.copyV1Battery;
@@ -927,9 +928,12 @@
         pw.println("Battery service (battery) commands:");
         pw.println("  help");
         pw.println("    Print this help text.");
-        pw.println("  get [-f] [ac|usb|wireless|dock|status|level|temp|present|counter|invalid]");
-        pw.println("  set [-f] "
-                + "[ac|usb|wireless|dock|status|level|temp|present|counter|invalid] <value>");
+        String getSetOptions = "ac|usb|wireless|dock|status|level|temp|present|counter|invalid";
+        if (batteryServiceSupportCurrentAdbCommand()) {
+            getSetOptions += "|current_now|current_average";
+        }
+        pw.println("  get [-f] [" + getSetOptions + "]");
+        pw.println("  set [-f] [" + getSetOptions + "] <value>");
         pw.println("    Force a battery property value, freezing battery state.");
         pw.println("    -f: force a battery change broadcast be sent, prints new sequence.");
         pw.println("  unplug [-f]");
@@ -1001,6 +1005,16 @@
                     case "counter":
                         pw.println(mHealthInfo.batteryChargeCounterUah);
                         break;
+                    case "current_now":
+                        if (batteryServiceSupportCurrentAdbCommand()) {
+                            pw.println(mHealthInfo.batteryCurrentMicroamps);
+                        }
+                        break;
+                    case "current_average":
+                        if (batteryServiceSupportCurrentAdbCommand()) {
+                            pw.println(mHealthInfo.batteryCurrentAverageMicroamps);
+                        }
+                        break;
                     case "temp":
                         pw.println(mHealthInfo.batteryTemperatureTenthsCelsius);
                         break;
@@ -1058,6 +1072,16 @@
                         case "counter":
                             mHealthInfo.batteryChargeCounterUah = Integer.parseInt(value);
                             break;
+                        case "current_now":
+                            if (batteryServiceSupportCurrentAdbCommand()) {
+                                mHealthInfo.batteryCurrentMicroamps = Integer.parseInt(value);
+                            }
+                            break;
+                        case "current_average":
+                            if (batteryServiceSupportCurrentAdbCommand()) {
+                                mHealthInfo.batteryCurrentAverageMicroamps =
+                                        Integer.parseInt(value);
+                            }
                         case "temp":
                             mHealthInfo.batteryTemperatureTenthsCelsius = Integer.parseInt(value);
                             break;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3e533a6..ac173f3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -11972,6 +11972,7 @@
         boolean dumpSwapPss;
         boolean dumpProto;
         boolean mDumpPrivateDirty;
+        boolean mDumpAllocatorStats;
     }
 
     @NeverCompile // Avoid size overhead of debugging code.
@@ -11991,6 +11992,7 @@
         opts.dumpSwapPss = false;
         opts.dumpProto = asProto;
         opts.mDumpPrivateDirty = false;
+        opts.mDumpAllocatorStats = false;
 
         int opti = 0;
         while (opti < args.length) {
@@ -12027,7 +12029,8 @@
                 opts.isCheckinRequest = true;
             } else if ("--proto".equals(opt)) {
                 opts.dumpProto = true;
-
+            } else if ("--logstats".equals(opt)) {
+                opts.mDumpAllocatorStats = true;
             } else if ("-h".equals(opt)) {
                 pw.println("meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]");
                 pw.println("  -a: include all available information for each process.");
@@ -12238,7 +12241,8 @@
                             try {
                                 thread.dumpMemInfo(tp.getWriteFd(),
                                         mi, opts.isCheckinRequest, opts.dumpFullDetails,
-                                        opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable, innerArgs);
+                                        opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable,
+                                        opts.mDumpAllocatorStats, innerArgs);
                                 tp.go(fd, opts.dumpUnreachable ? 30000 : 5000);
                             } finally {
                                 tp.kill();
@@ -17510,6 +17514,8 @@
      *         other {@code ActivityManager#USER_OP_*} codes for failure.
      *
      */
+    // TODO(b/302662311): Add javadoc changes corresponding to the user property that allows
+    // delayed locking behavior once the private space flag is finalized.
     @Override
     public int stopUserWithDelayedLocking(final int userId, boolean force,
             final IStopUserCallback callback) {
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index caafb42..fc8ad6bc 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -35,6 +35,7 @@
 import android.app.ForegroundServiceDelegationOptions;
 import android.content.ComponentName;
 import android.content.pm.ServiceInfo;
+import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.IntArray;
 import android.util.LongArray;
@@ -134,6 +135,11 @@
      * call of the right type will also be associated and logged
      */
     public void logForegroundServiceStart(int uid, int pid, ServiceRecord record) {
+        if (record.getComponentName() != null) {
+            final String traceTag = record.getComponentName().flattenToString() + ":" + uid;
+            Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, traceTag,
+                    "foregroundService", record.foregroundServiceType);
+        }
         // initialize the UID stack
         UidState uidState = mUids.get(uid);
         if (uidState == null) {
@@ -205,6 +211,11 @@
         // we need to log all the API end events and remove the start events
         // then we remove the FGS from the various stacks
         // and also clean up the start calls stack by UID
+        if (record.getComponentName() != null) {
+            final String traceTag = record.getComponentName().flattenToString() + ":" + uid;
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    traceTag, record.hashCode());
+        }
         final IntArray apiTypes = convertFgsTypeToApiTypes(record.foregroundServiceType);
         final UidState uidState = mUids.get(uid);
         if (apiTypes.size() == 0) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 47a99fe..a6b532c 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -356,6 +356,8 @@
      * Once total number of unlocked users reach mMaxRunningUsers, least recently used user
      * will be locked.
      */
+    // TODO(b/302662311): Add javadoc changes corresponding to the user property that allows
+    // delayed locking behavior once the private space flag is finalized.
     @GuardedBy("mLock")
     private boolean mDelayUserDataLocking;
 
@@ -365,11 +367,12 @@
     private volatile boolean mAllowUserUnlocking;
 
     /**
-     * Keep track of last active users for mDelayUserDataLocking.
-     * The latest stopped user is placed in front while the least recently stopped user in back.
+     * Keep track of last active users for delayUserDataLocking.
+     * The most recently stopped user with delayed locking is placed in front, while the least
+     * recently stopped user in back.
      */
     @GuardedBy("mLock")
-    private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>();
+    private final ArrayList<Integer> mLastActiveUsersForDelayedLocking = new ArrayList<>();
 
     /**
      * Map of userId to {@link UserCompletedEventType} event flags, indicating which as-yet-
@@ -1011,20 +1014,21 @@
         Slogf.i(TAG, "stopSingleUserLU userId=" + userId);
         final UserState uss = mStartedUsers.get(userId);
         if (uss == null) {  // User is not started
-            // If mDelayUserDataLocking is set and allowDelayedLocking is not set, we need to lock
-            // the requested user as the client wants to stop and lock the user. On the other hand,
-            // having keyEvictedCallback set will lead into locking user if mDelayUserDataLocking
-            // is set as that means client wants to lock the user immediately.
-            // If mDelayUserDataLocking is not set, the user was already locked when it was stopped
-            // and no further action is necessary.
-            if (mDelayUserDataLocking) {
+            // If canDelayDataLockingForUser() is true and allowDelayedLocking is false, we need
+            // to lock the requested user as the client wants to stop and lock the user. On the
+            // other hand, having keyEvictedCallback set will lead into locking user if
+            // canDelayDataLockingForUser() is true as that means client wants to lock the user
+            // immediately.
+            // If canDelayDataLockingForUser() is false, the user was already locked when it was
+            // stopped and no further action is necessary.
+            if (canDelayDataLockingForUser(userId)) {
                 if (allowDelayedLocking && keyEvictedCallback != null) {
                     Slogf.wtf(TAG, "allowDelayedLocking set with KeyEvictedCallback, ignore it"
                             + " and lock user:" + userId, new RuntimeException());
                     allowDelayedLocking = false;
                 }
                 if (!allowDelayedLocking) {
-                    if (mLastActiveUsers.remove(Integer.valueOf(userId))) {
+                    if (mLastActiveUsersForDelayedLocking.remove(Integer.valueOf(userId))) {
                         // should lock the user, user is already gone
                         final ArrayList<KeyEvictedCallback> keyEvictedCallbacks;
                         if (keyEvictedCallback != null) {
@@ -1354,14 +1358,21 @@
     @GuardedBy("mLock")
     private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) {
         int userIdToLock = userId;
-        if (mDelayUserDataLocking && allowDelayedLocking && !getUserInfo(userId).isEphemeral()
+        // TODO: Decouple the delayed locking flows from mMaxRunningUsers or rename the property to
+        // state maximum running unlocked users specifically
+        if (canDelayDataLockingForUser(userIdToLock) && allowDelayedLocking
+                && !getUserInfo(userId).isEphemeral()
                 && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
-            mLastActiveUsers.remove((Integer) userId); // arg should be object, not index
-            mLastActiveUsers.add(0, userId);
-            int totalUnlockedUsers = mStartedUsers.size() + mLastActiveUsers.size();
+            // arg should be object, not index
+            mLastActiveUsersForDelayedLocking.remove((Integer) userId);
+            mLastActiveUsersForDelayedLocking.add(0, userId);
+            int totalUnlockedUsers = mStartedUsers.size()
+                    + mLastActiveUsersForDelayedLocking.size();
             if (totalUnlockedUsers > mMaxRunningUsers) { // should lock a user
-                userIdToLock = mLastActiveUsers.get(mLastActiveUsers.size() - 1);
-                mLastActiveUsers.remove(mLastActiveUsers.size() - 1);
+                userIdToLock = mLastActiveUsersForDelayedLocking.get(
+                        mLastActiveUsersForDelayedLocking.size() - 1);
+                mLastActiveUsersForDelayedLocking
+                        .remove(mLastActiveUsersForDelayedLocking.size() - 1);
                 Slogf.i(TAG, "finishUserStopped, stopping user:" + userId
                         + " lock user:" + userIdToLock);
             } else {
@@ -1374,6 +1385,24 @@
     }
 
     /**
+     * Returns whether the user can have its CE storage left unlocked, even when it is stopped,
+     * either due to a global device configuration or an individual user's property.
+     */
+    private boolean canDelayDataLockingForUser(@UserIdInt int userIdToLock) {
+        if (allowBiometricUnlockForPrivateProfile()) {
+            final UserProperties userProperties = getUserProperties(userIdToLock);
+            return (mDelayUserDataLocking || (userProperties != null
+                    && userProperties.getAllowStoppingUserWithDelayedLocking()));
+        }
+        return mDelayUserDataLocking;
+    }
+
+    private boolean allowBiometricUnlockForPrivateProfile() {
+        return android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace();
+    }
+
+    /**
      * Determines the list of users that should be stopped together with the specified
      * {@code userId}. The returned list includes {@code userId}.
      */
@@ -3161,7 +3190,7 @@
             pw.println("  mCurrentProfileIds:" + Arrays.toString(mCurrentProfileIds));
             pw.println("  mCurrentUserId:" + mCurrentUserId);
             pw.println("  mTargetUserId:" + mTargetUserId);
-            pw.println("  mLastActiveUsers:" + mLastActiveUsers);
+            pw.println("  mLastActiveUsersForDelayedLocking:" + mLastActiveUsersForDelayedLocking);
             pw.println("  mDelayUserDataLocking:" + mDelayUserDataLocking);
             pw.println("  mAllowUserUnlocking:" + mAllowUserUnlocking);
             pw.println("  shouldStopUserOnSwitch():" + shouldStopUserOnSwitch());
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index ffdab7d..9ae43a0 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -235,9 +235,10 @@
      * {@link AdiDeviceState#toPersistableString()}.
      */
     public static int getPeristedMaxSize() {
-        return 36;  /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+        return 39;  /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1
                            + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1
-                           + (SETTINGS_FIELD_SEPARATOR)5 */
+                           + (mAudioDeviceCategory)1 + (SETTINGS_FIELD_SEPARATOR)6
+                           + (SETTING_DEVICE_SEPARATOR)1 */
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 9cfcb16..8091753 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2037,9 +2037,8 @@
                     } break;
 
                 case MSG_L_UPDATED_ADI_DEVICE_STATE:
-                    synchronized (mDeviceStateLock) {
-                        mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj);
-                    } break;
+                    mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj);
+                    break;
 
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
@@ -2687,11 +2686,15 @@
             return;
         }
         final SettingsAdapter settingsAdapter = mAudioService.getSettings();
-        boolean res = settingsAdapter.putSecureStringForUser(mAudioService.getContentResolver(),
-                Settings.Secure.AUDIO_DEVICE_INVENTORY,
-                deviceSettings, UserHandle.USER_CURRENT);
-        if (!res) {
-            Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings);
+        try {
+            boolean res = settingsAdapter.putSecureStringForUser(mAudioService.getContentResolver(),
+                    Settings.Secure.AUDIO_DEVICE_INVENTORY,
+                    deviceSettings, UserHandle.USER_CURRENT);
+            if (!res) {
+                Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings);
+            }
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings, e);
         }
     }
 
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index d077ebc..34cfdfa 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -30,7 +30,6 @@
 import static android.media.AudioSystem.isBluetoothScoOutDevice;
 import static android.media.audio.Flags.automaticBtDeviceType;
 
-
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
 import android.annotation.NonNull;
@@ -81,7 +80,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
@@ -104,6 +102,14 @@
     private static final String SETTING_DEVICE_SEPARATOR_CHAR = "|";
     private static final String SETTING_DEVICE_SEPARATOR = "\\|";
 
+    /** Max String length that can be persisted within the Settings. */
+    // LINT.IfChange(settings_max_length_per_string)
+    private static final int MAX_SETTINGS_LENGTH_PER_STRING = 32768;
+    // LINT.ThenChange(/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java)
+
+    private static final int MAX_DEVICE_INVENTORY_ENTRIES =
+            MAX_SETTINGS_LENGTH_PER_STRING / AdiDeviceState.getPeristedMaxSize();
+
     // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices
     private final Object mDevicesLock = new Object();
 
@@ -113,7 +119,8 @@
     private final Object mDeviceInventoryLock = new Object();
 
     @GuardedBy("mDeviceInventoryLock")
-    private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>();
+    private final LinkedHashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory =
+            new LinkedHashMap<>();
 
     Collection<AdiDeviceState> getImmutableDeviceInventory() {
         final List<AdiDeviceState> newList;
@@ -136,6 +143,7 @@
                 oldState.setSAEnabled(newState.isSAEnabled());
                 return oldState;
             });
+            checkDeviceInventorySize_l();
         }
         mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
     }
@@ -173,6 +181,8 @@
             ads.setAudioDeviceCategory(category);
 
             mDeviceInventory.put(ads.getDeviceId(), ads);
+            checkDeviceInventorySize_l();
+
             mDeviceBroker.postUpdatedAdiDeviceState(ads);
             mDeviceBroker.postPersistAudioDeviceSettings();
         }
@@ -200,6 +210,7 @@
                         }
                         return oldState;
                     });
+            checkDeviceInventorySize_l();
         }
         if (updatedCategory.get()) {
             mDeviceBroker.postUpdatedAdiDeviceState(deviceState);
@@ -272,6 +283,18 @@
         }
     }
 
+    @GuardedBy("mDeviceInventoryLock")
+    private void checkDeviceInventorySize_l() {
+        if (mDeviceInventory.size() > MAX_DEVICE_INVENTORY_ENTRIES) {
+            // remove the first element
+            Iterator<Entry<Pair<Integer, String>, AdiDeviceState>> iterator =
+                    mDeviceInventory.entrySet().iterator();
+            if (iterator.hasNext()) {
+                iterator.remove();
+            }
+        }
+    }
+
     @GuardedBy({"mDevicesLock", "mDeviceInventoryLock"})
     private boolean synchronizeBleDeviceInInventory(AdiDeviceState updatedDevice) {
         for (DeviceInfo di : mConnectedDevices.values()) {
@@ -2806,7 +2829,7 @@
             deviceCatalogSize = mDeviceInventory.size();
 
             final StringBuilder settingsBuilder = new StringBuilder(
-                            deviceCatalogSize * AdiDeviceState.getPeristedMaxSize());
+                    deviceCatalogSize * AdiDeviceState.getPeristedMaxSize());
 
             Iterator<AdiDeviceState> iterator = mDeviceInventory.values().iterator();
             if (iterator.hasNext()) {
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 3b7d80c..1e5e147 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1292,7 +1292,11 @@
      */
     private boolean isPackageStopped(String packageName, int userId) {
         if (android.content.pm.Flags.stayStopped()) {
-            return mPackageManagerInternal.isPackageStopped(packageName, userId);
+            try {
+                return mPackageManagerInternal.isPackageStopped(packageName, userId);
+            } catch (IllegalArgumentException e) {
+                Log.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
+            }
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 3529b04..b1b1dba 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -122,11 +122,10 @@
 
     /**
      * Flag: This flag identifies secondary displays that should show system decorations, such as
-     * status bar, navigation bar, home activity or IME.
+     * navigation bar, home activity or wallpaper.
      * <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p>
      * @hide
      */
-    // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors
     public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 12;
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index dcb86a7..66807ae 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -23,7 +23,9 @@
 import android.annotation.RequiresPermission;
 import android.annotation.UiThread;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.pm.PackageManagerInternal;
+import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerGlobal;
 import android.os.Handler;
 import android.os.IBinder;
@@ -64,6 +66,7 @@
     private static final int LONG_EVENT_BUFFER_SIZE = EVENT_BUFFER_SIZE * 20;
     private static final long HANDWRITING_DELEGATION_IDLE_TIMEOUT_MS = 3000;
 
+    private final Context mContext;
     // This must be the looper for the UiThread.
     private final Looper mLooper;
     private final InputManagerInternal mInputManagerInternal;
@@ -87,7 +90,9 @@
     private int mCurrentRequestId;
 
     @AnyThread
-    HandwritingModeController(Looper uiThreadLooper, Runnable inkWindowInitRunnable) {
+    HandwritingModeController(Context context, Looper uiThreadLooper,
+            Runnable inkWindowInitRunnable) {
+        mContext = context;
         mLooper = uiThreadLooper;
         mCurrentDisplayId = Display.INVALID_DISPLAY;
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
@@ -285,7 +290,14 @@
         mHandwritingSurface.startIntercepting(imePid, imeUid);
 
         // Unset the pointer icon for the stylus in case the app had set it.
-        InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
+        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+            Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon(
+                    PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED),
+                    downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0),
+                    mHandwritingSurface.getInputChannel().getToken());
+        } else {
+            InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
+        }
 
         return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(),
                 mHandwritingBuffer);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 09c388f..30e9f5b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1713,7 +1713,7 @@
                 com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
         mNonPreemptibleInputMethods = mRes.getStringArray(
                 com.android.internal.R.array.config_nonPreemptibleInputMethods);
-        mHwController = new HandwritingModeController(thread.getLooper(),
+        mHwController = new HandwritingModeController(mContext, thread.getLooper(),
                 new InkWindowInitializer());
         registerDeviceListenerAndCheckStylusSupport();
     }
@@ -5275,7 +5275,7 @@
                     return true;
                 }
             }
-            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
+            mSettings.appendAndPutEnabledInputMethodLocked(id);
             // Previous state was disabled.
             return false;
         } else {
@@ -5612,7 +5612,7 @@
                 if (enabled) {
                     if (!settings.getEnabledInputMethodListLocked().contains(
                             methodMap.get(imeId))) {
-                        settings.appendAndPutEnabledInputMethodLocked(imeId, false);
+                        settings.appendAndPutEnabledInputMethodLocked(imeId);
                     }
                 } else {
                     settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
@@ -5626,7 +5626,7 @@
         @Override
         public void setVirtualDeviceInputMethodForAllUsers(int deviceId, @Nullable String imeId) {
             // TODO(b/287269288): validate that id belongs to a valid virtual device instead.
-            Preconditions.checkArgument(deviceId == Context.DEVICE_ID_DEFAULT,
+            Preconditions.checkArgument(deviceId != Context.DEVICE_ID_DEFAULT,
                     "DeviceId " + deviceId + " does not belong to a virtual device.");
             synchronized (ImfLock.class) {
                 if (imeId == null) {
@@ -6357,7 +6357,7 @@
                         }
                     }
                     if (!previouslyEnabled) {
-                        settings.appendAndPutEnabledInputMethodLocked(imeId, false);
+                        settings.appendAndPutEnabledInputMethodLocked(imeId);
                     }
                 }
             } else {
@@ -6501,7 +6501,7 @@
                         settings.putEnabledInputMethodsStr("");
                         nextEnabledImes.forEach(
                                 imi -> settings.appendAndPutEnabledInputMethodLocked(
-                                        imi.getId(), false));
+                                        imi.getId()));
 
                         // Reset selected IME.
                         settings.putSelectedInputMethod(nextIme);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index acf9a7f..2128356 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -362,10 +362,7 @@
             return result;
         }
 
-        void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
-            if (reloadInputMethodStr) {
-                getEnabledInputMethodsStr();
-            }
+        void appendAndPutEnabledInputMethodLocked(String id) {
             if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
                 // Add in the newly enabled input method.
                 putEnabledInputMethodsStr(id);
diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
index a00999d..7cf3983 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
@@ -39,6 +39,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.media.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -219,7 +220,10 @@
         BluetoothRouteInfo
                 newBtRoute = new BluetoothRouteInfo();
         newBtRoute.mBtDevice = device;
-        String deviceName = device.getName();
+        String deviceName =
+                Flags.enableUseOfBluetoothDeviceGetAliasForMr2infoGetName()
+                        ? device.getAlias()
+                        : device.getName();
         if (TextUtils.isEmpty(deviceName)) {
             deviceName = mContext.getResources().getText(R.string.unknownName).toString();
         }
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index 041fceaf..ede2d27 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -43,6 +43,7 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.R;
+import com.android.media.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -283,7 +284,10 @@
         newBtRoute.mBtDevice = device;
 
         String routeId = device.getAddress();
-        String deviceName = device.getName();
+        String deviceName =
+                Flags.enableUseOfBluetoothDeviceGetAliasForMr2infoGetName()
+                        ? device.getAlias()
+                        : device.getName();
         if (TextUtils.isEmpty(deviceName)) {
             deviceName = mContext.getResources().getText(R.string.unknownName).toString();
         }
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index 9fdeda4..8855666 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -16,14 +16,23 @@
 
 package com.android.server.notification;
 
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+
 import android.app.UiModeManager;
 import android.app.WallpaperManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.display.ColorDisplayManager;
 import android.os.Binder;
 import android.os.PowerManager;
 import android.service.notification.DeviceEffectsApplier;
 import android.service.notification.ZenDeviceEffects;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
+
+import com.android.internal.annotations.GuardedBy;
 
 /** Default implementation for {@link DeviceEffectsApplier}. */
 class DefaultDeviceEffectsApplier implements DeviceEffectsApplier {
@@ -34,13 +43,24 @@
     private static final int SATURATION_LEVEL_FULL_COLOR = 100;
     private static final float WALLPAPER_DIM_AMOUNT_DIMMED = 0.6f;
     private static final float WALLPAPER_DIM_AMOUNT_NORMAL = 0f;
+    private static final IntentFilter SCREEN_OFF_INTENT_FILTER = new IntentFilter(
+            Intent.ACTION_SCREEN_OFF);
 
+    private final Context mContext;
     private final ColorDisplayManager mColorDisplayManager;
     private final PowerManager mPowerManager;
     private final UiModeManager mUiModeManager;
     private final WallpaperManager mWallpaperManager;
 
+    private final Object mRegisterReceiverLock = new Object();
+    @GuardedBy("mRegisterReceiverLock")
+    private boolean mIsScreenOffReceiverRegistered;
+
+    private ZenDeviceEffects mLastAppliedEffects = new ZenDeviceEffects.Builder().build();
+    private boolean mPendingNightMode;
+
     DefaultDeviceEffectsApplier(Context context) {
+        mContext = context;
         mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
         mPowerManager = context.getSystemService(PowerManager.class);
         mUiModeManager = context.getSystemService(UiModeManager.class);
@@ -48,24 +68,90 @@
     }
 
     @Override
-    public void apply(ZenDeviceEffects effects) {
+    public void apply(ZenDeviceEffects effects, @ConfigChangeOrigin int origin) {
         Binder.withCleanCallingIdentity(() -> {
-            mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN,
-                    effects.shouldSuppressAmbientDisplay());
-
-            if (mColorDisplayManager != null) {
-                mColorDisplayManager.setSaturationLevel(
-                        effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE
-                                : SATURATION_LEVEL_FULL_COLOR);
+            if (mLastAppliedEffects.shouldSuppressAmbientDisplay()
+                    != effects.shouldSuppressAmbientDisplay()) {
+                mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN,
+                        effects.shouldSuppressAmbientDisplay());
             }
 
-            if (mWallpaperManager != null) {
-                mWallpaperManager.setWallpaperDimAmount(
-                        effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED
-                                : WALLPAPER_DIM_AMOUNT_NORMAL);
+            if (mLastAppliedEffects.shouldDisplayGrayscale() != effects.shouldDisplayGrayscale()) {
+                if (mColorDisplayManager != null) {
+                    mColorDisplayManager.setSaturationLevel(
+                            effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE
+                                    : SATURATION_LEVEL_FULL_COLOR);
+                }
             }
 
-            // TODO: b/308673343 - Apply dark theme (via UiModeManager) when screen is off.
+            if (mLastAppliedEffects.shouldDimWallpaper() != effects.shouldDimWallpaper()) {
+                if (mWallpaperManager != null) {
+                    mWallpaperManager.setWallpaperDimAmount(
+                            effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED
+                                    : WALLPAPER_DIM_AMOUNT_NORMAL);
+                }
+            }
+
+            if (mLastAppliedEffects.shouldUseNightMode() != effects.shouldUseNightMode()) {
+                updateOrScheduleNightMode(effects.shouldUseNightMode(), origin);
+            }
         });
+
+        mLastAppliedEffects = effects;
+    }
+
+    private void updateOrScheduleNightMode(boolean useNightMode, @ConfigChangeOrigin int origin) {
+        mPendingNightMode = useNightMode;
+
+        // Changing the theme can be disruptive for the user (Activities are likely recreated, may
+        // lose some state). Therefore we only apply the change immediately if the rule was
+        // activated manually, or we are initializing, or the screen is currently off/dreaming.
+        if (origin == ZenModeConfig.UPDATE_ORIGIN_INIT
+                || origin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER
+                || origin == ZenModeConfig.UPDATE_ORIGIN_USER
+                || origin == ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                || !mPowerManager.isInteractive()) {
+            unregisterScreenOffReceiver();
+            updateNightModeImmediately(useNightMode);
+        } else {
+            registerScreenOffReceiver();
+        }
+    }
+
+    @GuardedBy("mRegisterReceiverLock")
+    private final BroadcastReceiver mNightModeWhenScreenOff = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            unregisterScreenOffReceiver();
+            updateNightModeImmediately(mPendingNightMode);
+        }
+    };
+
+    private void updateNightModeImmediately(boolean useNightMode) {
+        Binder.withCleanCallingIdentity(() -> {
+            // TODO: b/314285749 - Placeholder; use real APIs when available.
+            mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+            mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
+                    useNightMode);
+        });
+    }
+
+    private void registerScreenOffReceiver() {
+        synchronized (mRegisterReceiverLock) {
+            if (!mIsScreenOffReceiverRegistered) {
+                mContext.registerReceiver(mNightModeWhenScreenOff, SCREEN_OFF_INTENT_FILTER,
+                        Context.RECEIVER_NOT_EXPORTED);
+                mIsScreenOffReceiverRegistered = true;
+            }
+        }
+    }
+
+    private void unregisterScreenOffReceiver() {
+        synchronized (mRegisterReceiverLock) {
+            if (mIsScreenOffReceiverRegistered) {
+                mIsScreenOffReceiverRegistered = false;
+                mContext.unregisterReceiver(mNightModeWhenScreenOff);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 02845fb..c2a145d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5299,11 +5299,11 @@
         public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
             enforceSystemOrSystemUI("INotificationManager.setZenMode");
             final int callingUid = Binder.getCallingUid();
-            final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
             final long identity = Binder.clearCallingIdentity();
             try {
-                mZenModeHelper.setManualZenMode(mode, conditionId, null, reason, callingUid,
-                        isSystemOrSystemUi);
+                mZenModeHelper.setManualZenMode(mode, conditionId,
+                        ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, // Checked by enforce()
+                        reason, /* caller= */ null, callingUid);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -5360,10 +5360,11 @@
             }
 
             return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
-                    "addAutomaticZenRule", Binder.getCallingUid(),
-                    // TODO: b/308670715: Distinguish FROM_APP from FROM_USER
-                    isCallerSystemOrSystemUi() ? ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI
-                            : ZenModeHelper.FROM_APP);
+                    // TODO: b/308670715: Distinguish origin properly (e.g. USER if creating a rule
+                    //  manually in Settings).
+                    isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                            : ZenModeConfig.UPDATE_ORIGIN_APP,
+                    "addAutomaticZenRule", Binder.getCallingUid());
         }
 
         @Override
@@ -5379,11 +5380,12 @@
             Objects.requireNonNull(automaticZenRule.getConditionId(), "ConditionId is null");
             enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule");
 
+            // TODO: b/308670715: Distinguish origin properly (e.g. USER if updating a rule
+            //  manually in Settings).
             return mZenModeHelper.updateAutomaticZenRule(id, automaticZenRule,
-                    "updateAutomaticZenRule", Binder.getCallingUid(),
-                    // TODO: b/308670715: Distinguish FROM_APP from FROM_USER
-                    isCallerSystemOrSystemUi() ? ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI
-                            : ZenModeHelper.FROM_APP);
+                    isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                            : ZenModeConfig.UPDATE_ORIGIN_APP,
+                    "updateAutomaticZenRule", Binder.getCallingUid());
         }
 
         @Override
@@ -5392,8 +5394,12 @@
             // Verify that they can modify zen rules.
             enforcePolicyAccess(Binder.getCallingUid(), "removeAutomaticZenRule");
 
-            return mZenModeHelper.removeAutomaticZenRule(id, "removeAutomaticZenRule",
-                    Binder.getCallingUid(), isCallerSystemOrSystemUi());
+            // TODO: b/308670715: Distinguish origin properly (e.g. USER if removing a rule
+            //  manually in Settings).
+            return mZenModeHelper.removeAutomaticZenRule(id,
+                    isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                            : ZenModeConfig.UPDATE_ORIGIN_APP,
+                    "removeAutomaticZenRule", Binder.getCallingUid());
         }
 
         @Override
@@ -5402,8 +5408,9 @@
             enforceSystemOrSystemUI("removeAutomaticZenRules");
 
             return mZenModeHelper.removeAutomaticZenRules(packageName,
-                    packageName + "|removeAutomaticZenRules", Binder.getCallingUid(),
-                    isCallerSystemOrSystemUi());
+                    isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                            : ZenModeConfig.UPDATE_ORIGIN_APP,
+                    packageName + "|removeAutomaticZenRules", Binder.getCallingUid());
         }
 
         @Override
@@ -5421,8 +5428,12 @@
 
             enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
 
-            mZenModeHelper.setAutomaticZenRuleState(id, condition, Binder.getCallingUid(),
-                    isCallerSystemOrSystemUi());
+            // TODO: b/308670715: Distinguish origin properly (e.g. USER if toggling a rule
+            //  manually in Settings).
+            mZenModeHelper.setAutomaticZenRuleState(id, condition,
+                    isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                            : ZenModeConfig.UPDATE_ORIGIN_APP,
+                    Binder.getCallingUid());
         }
 
         @Override
@@ -5440,8 +5451,11 @@
 
             final long identity = Binder.clearCallingIdentity();
             try {
-                mZenModeHelper.setManualZenMode(zen, null, pkg, "setInterruptionFilter",
-                        callingUid, isSystemOrSystemUi);
+                mZenModeHelper.setManualZenMode(zen, null,
+                        isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                                : ZenModeConfig.UPDATE_ORIGIN_APP,
+                        /* reason= */ "setInterruptionFilter", /* caller= */ pkg,
+                        callingUid);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -5806,7 +5820,10 @@
                 } else {
                     ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion,
                             policy);
-                    mZenModeHelper.setNotificationPolicy(policy, callingUid, isSystemOrSystemUi);
+                    mZenModeHelper.setNotificationPolicy(policy,
+                            isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                                    : ZenModeConfig.UPDATE_ORIGIN_APP,
+                            callingUid);
                 }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to set notification policy", e);
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 6a7eebb..fd86870 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -59,6 +59,7 @@
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.RankingHelperProto;
+import android.service.notification.ZenModeConfig;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
@@ -1862,12 +1863,16 @@
     public void updateZenPolicy(boolean areChannelsBypassingDnd, int callingUid,
             boolean fromSystemOrSystemUi) {
         NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
-        mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
-                policy.priorityCategories, policy.priorityCallSenders,
-                policy.priorityMessageSenders, policy.suppressedVisualEffects,
-                (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
-                        : 0),
-                policy.priorityConversationSenders), callingUid, fromSystemOrSystemUi);
+        mZenModeHelper.setNotificationPolicy(
+                new NotificationManager.Policy(
+                        policy.priorityCategories, policy.priorityCallSenders,
+                        policy.priorityMessageSenders, policy.suppressedVisualEffects,
+                        (areChannelsBypassingDnd
+                                ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND : 0),
+                        policy.priorityConversationSenders),
+                fromSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                        : ZenModeConfig.UPDATE_ORIGIN_APP,
+                callingUid);
     }
 
     // TODO: b/310620812 - rename to hasPriorityChannels() when modes_api is inlined.
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index 6ecd799..86aa2d8 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -111,8 +111,10 @@
     public void onServiceAdded(ComponentName component) {
         if (DEBUG) Log.d(TAG, "onServiceAdded " + component);
         final int callingUid = Binder.getCallingUid();
-        mHelper.setConfig(mHelper.getConfig(), component, "zmc.onServiceAdded:" + component,
-                callingUid, callingUid == Process.SYSTEM_UID);
+        mHelper.setConfig(mHelper.getConfig(), component,
+                callingUid == Process.SYSTEM_UID ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                        : ZenModeConfig.UPDATE_ORIGIN_APP,
+                "zmc.onServiceAdded:" + component, callingUid);
     }
 
     @Override
@@ -121,8 +123,10 @@
         ZenModeConfig config = mHelper.getConfig();
         if (config == null) return;
         final int callingUid = Binder.getCallingUid();
-        mHelper.setAutomaticZenRuleState(id, condition, callingUid,
-                callingUid == Process.SYSTEM_UID);
+        mHelper.setAutomaticZenRuleState(id, condition,
+                callingUid == Process.SYSTEM_UID ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                        : ZenModeConfig.UPDATE_ORIGIN_APP,
+                callingUid);
     }
 
     // Only valid for CPS backed rules
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 218519f..7ec94c3 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2014, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,11 +24,16 @@
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN;
 import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
 import static android.service.notification.NotificationServiceProto.ROOT_CONFIG;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
 
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 
 import android.annotation.DrawableRes;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -79,6 +84,7 @@
 import android.service.notification.DeviceEffectsApplier;
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
 import android.service.notification.ZenModeConfig.ZenRule;
 import android.service.notification.ZenModeProto;
 import android.service.notification.ZenPolicy;
@@ -111,8 +117,6 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -137,21 +141,6 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     static final long SEND_ACTIVATION_AZR_STATUSES = 308673617L;
 
-    /** A rule addition or update that is initiated by the System or SystemUI. */
-    static final int FROM_SYSTEM_OR_SYSTEMUI = 1;
-    /** A rule addition or update that is initiated by the user (through system settings). */
-    static final int FROM_USER = 2;
-    /** A rule addition or update that is initiated by an app (via NotificationManager APIs). */
-    static final int FROM_APP = 3;
-
-    @IntDef(prefix = { "FROM_" }, value = {
-            FROM_SYSTEM_OR_SYSTEMUI,
-            FROM_USER,
-            FROM_APP
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface ChangeOrigin {}
-
     // pkg|userId => uid
     @VisibleForTesting protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>();
 
@@ -160,7 +149,7 @@
     private final SettingsObserver mSettingsObserver;
     private final AppOpsManager mAppOps;
     private final NotificationManager mNotificationManager;
-    private ZenModeConfig mDefaultConfig;
+    private final ZenModeConfig mDefaultConfig;
     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
     private final ZenModeFiltering mFiltering;
     private final RingerModeDelegate mRingerModeDelegate = new
@@ -275,9 +264,8 @@
             // "update" config to itself, which will have no effect in the case where a config
             // was read in via XML, but will initialize zen mode if nothing was read in and the
             // config remains the default.
-            updateConfigAndZenModeLocked(mConfig, "init", true /*setRingerMode*/,
-                    Process.SYSTEM_UID /* callingUid */, true /* is system */,
-                    false /* no broadcasts*/);
+            updateConfigAndZenModeLocked(mConfig, UPDATE_ORIGIN_INIT, "init",
+                    true /*setRingerMode*/, Process.SYSTEM_UID /* callingUid */);
         }
     }
 
@@ -297,24 +285,20 @@
     /**
      * Set the {@link DeviceEffectsApplier} used to apply the consolidated effects.
      *
-     * <p>If effects were calculated previously (for example, when we loaded a {@link ZenModeConfig}
-     * that includes activated rules), they will be applied immediately.
+     * <p>Previously calculated effects (as loaded from the user's {@link ZenModeConfig}) will be
+     * applied immediately.
      */
     void setDeviceEffectsApplier(@NonNull DeviceEffectsApplier deviceEffectsApplier) {
         if (!Flags.modesApi()) {
             return;
         }
-        ZenDeviceEffects consolidatedDeviceEffects;
         synchronized (mConfigLock) {
             if (mDeviceEffectsApplier != null) {
                 throw new IllegalStateException("Already set up a DeviceEffectsApplier!");
             }
             mDeviceEffectsApplier = deviceEffectsApplier;
-            consolidatedDeviceEffects = mConsolidatedDeviceEffects;
         }
-        if (consolidatedDeviceEffects.hasEffects()) {
-            applyConsolidatedDeviceEffects();
-        }
+        applyConsolidatedDeviceEffects(UPDATE_ORIGIN_INIT);
     }
 
     public void onUserSwitched(int user) {
@@ -353,7 +337,8 @@
             config.user = user;
         }
         synchronized (mConfigLock) {
-            setConfigLocked(config, null, reason, Process.SYSTEM_UID, true);
+            setConfigLocked(config, null, UPDATE_ORIGIN_INIT_USER, reason,
+                    Process.SYSTEM_UID);
         }
         cleanUpZenRules();
     }
@@ -366,9 +351,11 @@
             boolean fromSystemOrSystemUi) {
         final int newZen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
         if (newZen != -1) {
-            setManualZenMode(newZen, null, name != null ? name.getPackageName() : null,
-                    "listener:" + (name != null ? name.flattenToShortString() : null),
-                    callingUid, fromSystemOrSystemUi);
+            setManualZenMode(newZen, null,
+                    fromSystemOrSystemUi ? UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI : UPDATE_ORIGIN_APP,
+                    /* reason= */ "listener:" + (name != null ? name.flattenToShortString() : null),
+                    /* caller= */ name != null ? name.getPackageName() : null,
+                    callingUid);
         }
     }
 
@@ -428,7 +415,7 @@
     }
 
     public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
-            String reason, int callingUid, @ChangeOrigin int origin) {
+            @ConfigChangeOrigin int origin, String reason, int callingUid) {
         if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
             PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
             if (component == null) {
@@ -462,10 +449,9 @@
             }
             newConfig = mConfig.copy();
             ZenRule rule = new ZenRule();
-            populateZenRule(pkg, automaticZenRule, rule, true, origin);
+            populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
             newConfig.automaticRules.put(rule.id, rule);
-            if (setConfigLocked(newConfig, reason, rule.component, true, callingUid,
-                    origin == FROM_SYSTEM_OR_SYSTEMUI)) {
+            if (setConfigLocked(newConfig, origin, reason, rule.component, true, callingUid)) {
                 return rule.id;
             } else {
                 throw new AndroidRuntimeException("Could not create rule");
@@ -474,7 +460,7 @@
     }
 
     public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
-            String reason, int callingUid, @ChangeOrigin int origin) {
+            @ConfigChangeOrigin int origin, String reason, int callingUid) {
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -502,9 +488,9 @@
                 }
             }
 
-            populateZenRule(rule.pkg, automaticZenRule, rule, false, origin);
-            return setConfigLocked(newConfig, reason, rule.component, true, callingUid,
-                    origin == FROM_SYSTEM_OR_SYSTEMUI);
+            populateZenRule(rule.pkg, automaticZenRule, rule, origin, /* isNew= */ false);
+            return setConfigLocked(newConfig, origin, reason,
+                    rule.component, true, callingUid);
         }
     }
 
@@ -541,8 +527,7 @@
                     Condition deactivated = new Condition(rule.conditionId,
                             mContext.getString(R.string.zen_mode_implicit_deactivated),
                             Condition.STATE_FALSE);
-                    setAutomaticZenRuleState(rule.id, deactivated,
-                            callingUid, /* fromSystemOrSystemUi= */ false);
+                    setAutomaticZenRuleState(rule.id, deactivated, UPDATE_ORIGIN_APP, callingUid);
                 }
             } else {
                 // Either create a new rule with a default ZenPolicy, or update an existing rule's
@@ -558,9 +543,8 @@
                 rule.condition = new Condition(rule.conditionId,
                         mContext.getString(R.string.zen_mode_implicit_activated),
                         Condition.STATE_TRUE);
-                setConfigLocked(newConfig, /* triggeringComponent= */ null,
-                        "applyGlobalZenModeAsImplicitZenRule",
-                        callingUid, /* fromSystemOrSystemUi= */ false);
+                setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+                        "applyGlobalZenModeAsImplicitZenRule", callingUid);
             }
         }
     }
@@ -595,9 +579,8 @@
             }
             // TODO: b/308673679 - Keep user customization of this rule!
             rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
-            setConfigLocked(newConfig, /* triggeringComponent= */ null,
-                    "applyGlobalPolicyAsImplicitZenRule",
-                    callingUid, /* fromSystemOrSystemUi= */ false);
+            setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+                    "applyGlobalPolicyAsImplicitZenRule", callingUid);
         }
     }
 
@@ -669,8 +652,8 @@
         return "implicit_" + forPackage;
     }
 
-    public boolean removeAutomaticZenRule(String id, String reason, int callingUid,
-            boolean fromSystemOrSystemUi) {
+    boolean removeAutomaticZenRule(String id, @ConfigChangeOrigin int origin, String reason,
+            int callingUid) {
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -695,13 +678,12 @@
             }
             dispatchOnAutomaticRuleStatusChanged(
                     mConfig.user, ruleToRemove.getPkg(), id, AUTOMATIC_RULE_STATUS_REMOVED);
-            return setConfigLocked(newConfig, reason, null, true, callingUid,
-                    fromSystemOrSystemUi);
+            return setConfigLocked(newConfig, origin, reason, null, true, callingUid);
         }
     }
 
-    public boolean removeAutomaticZenRules(String packageName, String reason, int callingUid,
-            boolean fromSystemOrSystemUi) {
+    boolean removeAutomaticZenRules(String packageName, @ConfigChangeOrigin int origin,
+            String reason, int callingUid) {
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -712,13 +694,12 @@
                     newConfig.automaticRules.removeAt(i);
                 }
             }
-            return setConfigLocked(newConfig, reason, null, true, callingUid,
-                    fromSystemOrSystemUi);
+            return setConfigLocked(newConfig, origin, reason, null, true, callingUid);
         }
     }
 
-    public void setAutomaticZenRuleState(String id, Condition condition, int callingUid,
-            boolean fromSystemOrSystemUi) {
+    void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin,
+            int callingUid) {
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return;
@@ -726,13 +707,12 @@
             newConfig = mConfig.copy();
             ArrayList<ZenRule> rules = new ArrayList<>();
             rules.add(newConfig.automaticRules.get(id));
-            setAutomaticZenRuleStateLocked(newConfig, rules, condition, callingUid,
-                    fromSystemOrSystemUi);
+            setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin, callingUid);
         }
     }
 
-    public void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition, int callingUid,
-            boolean fromSystemOrSystemUi) {
+    void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition,
+            @ConfigChangeOrigin int origin, int callingUid) {
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return;
@@ -740,20 +720,23 @@
 
             setAutomaticZenRuleStateLocked(newConfig,
                     findMatchingRules(newConfig, ruleDefinition, condition),
-                    condition, callingUid, fromSystemOrSystemUi);
+                    condition, origin, callingUid);
         }
     }
 
     @GuardedBy("mConfigLock")
     private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules,
-            Condition condition, int callingUid, boolean fromSystemOrSystemUi) {
+            Condition condition, @ConfigChangeOrigin int origin, int callingUid) {
         if (rules == null || rules.isEmpty()) return;
 
+        if (Flags.modesApi() && condition.source == Condition.SOURCE_USER_ACTION) {
+            origin = UPDATE_ORIGIN_USER; // Although coming from app, it's actually a user action.
+        }
+
         for (ZenRule rule : rules) {
             rule.condition = condition;
             updateSnoozing(rule);
-            setConfigLocked(config, rule.component, "conditionChanged", callingUid,
-                    fromSystemOrSystemUi);
+            setConfigLocked(config, rule.component, origin, "conditionChanged", callingUid);
         }
     }
 
@@ -857,7 +840,7 @@
                         // update default rule (if locale changed, name of rule will change)
                         currRule.name = defaultRule.name;
                         updateAutomaticZenRule(defaultRule.id, zenRuleToAutomaticZenRule(currRule),
-                                "locale changed", callingUid, FROM_SYSTEM_OR_SYSTEMUI);
+                                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "locale changed", callingUid);
                     }
                 }
             }
@@ -899,13 +882,12 @@
         return null;
     }
 
-    @VisibleForTesting
     void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
-            boolean isNew, @ChangeOrigin int origin) {
-            // TODO: b/308671593,b/311406021 - Handle origins more precisely:
-            //  - FROM_USER can override anything and updates bitmask of user-modified fields;
-            //  - FROM_SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
-            //  - FROM_APP can only update if not user-modified.
+            @ConfigChangeOrigin int origin, boolean isNew) {
+        // TODO: b/308671593,b/311406021 - Handle origins more precisely:
+        //  - USER can override anything and updates bitmask of user-modified fields;
+        //  - SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
+        //  - APP can only update if not user-modified.
         if (rule.enabled != automaticZenRule.isEnabled()) {
             rule.snoozing = false;
         }
@@ -951,12 +933,12 @@
      */
     @Nullable
     private static ZenDeviceEffects fixZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects,
-            @Nullable ZenDeviceEffects newEffects, @ChangeOrigin int origin) {
+            @Nullable ZenDeviceEffects newEffects, @ConfigChangeOrigin int origin) {
         // TODO: b/308671593,b/311406021 - Handle origins more precisely:
-        //  - FROM_USER can override anything and updates bitmask of user-modified fields;
-        //  - FROM_SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
-        //  - FROM_APP can only update if not user-modified.
-        if (origin == FROM_SYSTEM_OR_SYSTEMUI || origin == FROM_USER) {
+        //  - USER can override anything and updates bitmask of user-modified fields;
+        //  - SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
+        //  - APP can only update if not user-modified.
+        if (origin != UPDATE_ORIGIN_APP) {
             return newEffects;
         }
 
@@ -1033,16 +1015,16 @@
                 : AUTOMATIC_RULE_STATUS_DISABLED);
     }
 
-    public void setManualZenMode(int zenMode, Uri conditionId, String caller, String reason,
-            int callingUid, boolean fromSystemOrSystemUi) {
-        setManualZenMode(zenMode, conditionId, reason, caller, true /*setRingerMode*/, callingUid,
-                fromSystemOrSystemUi);
+    void setManualZenMode(int zenMode, Uri conditionId, @ConfigChangeOrigin int origin,
+            String reason, @Nullable String caller, int callingUid) {
+        setManualZenMode(zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/,
+                callingUid);
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 0);
     }
 
-    private void setManualZenMode(int zenMode, Uri conditionId, String reason, String caller,
-            boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) {
+    private void setManualZenMode(int zenMode, Uri conditionId, @ConfigChangeOrigin int origin,
+            String reason, @Nullable String caller, boolean setRingerMode, int callingUid) {
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return;
@@ -1069,8 +1051,7 @@
                 }
                 newConfig.manualRule = newRule;
             }
-            setConfigLocked(newConfig, reason, null, setRingerMode, callingUid,
-                    fromSystemOrSystemUi);
+            setConfigLocked(newConfig, origin, reason, null, setRingerMode, callingUid);
         }
     }
 
@@ -1199,7 +1180,9 @@
             }
             if (DEBUG) Log.d(TAG, reason);
             synchronized (mConfigLock) {
-                setConfigLocked(config, null, reason, Process.SYSTEM_UID, true);
+                setConfigLocked(config, null,
+                        forRestore ? UPDATE_ORIGIN_RESTORE_BACKUP : UPDATE_ORIGIN_INIT, reason,
+                        Process.SYSTEM_UID);
             }
         }
     }
@@ -1233,13 +1216,13 @@
     /**
      * Sets the global notification policy used for priority only do not disturb
      */
-    public void setNotificationPolicy(Policy policy, int callingUid, boolean fromSystemOrSystemUi) {
+    public void setNotificationPolicy(Policy policy, @ConfigChangeOrigin int origin,
+            int callingUid) {
         synchronized (mConfigLock) {
             if (policy == null || mConfig == null) return;
             final ZenModeConfig newConfig = mConfig.copy();
             newConfig.applyNotificationPolicy(policy);
-            setConfigLocked(newConfig, null, "setNotificationPolicy", callingUid,
-                    fromSystemOrSystemUi);
+            setConfigLocked(newConfig, null, origin, "setNotificationPolicy", callingUid);
         }
     }
 
@@ -1264,8 +1247,8 @@
                     }
                 }
             }
-            setConfigLocked(newConfig, null, "cleanUpZenRules", Process.SYSTEM_UID,
-                    true);
+            setConfigLocked(newConfig, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "cleanUpZenRules",
+                    Process.SYSTEM_UID);
         }
     }
 
@@ -1287,22 +1270,22 @@
 
     @GuardedBy("mConfigLock")
     private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
-            String reason, int callingUid, boolean fromSystemOrSystemUi) {
-        return setConfigLocked(config, reason, triggeringComponent, true /*setRingerMode*/,
-                callingUid, fromSystemOrSystemUi);
+            @ConfigChangeOrigin int origin, String reason, int callingUid) {
+        return setConfigLocked(config, origin, reason, triggeringComponent, true /*setRingerMode*/,
+                callingUid);
     }
 
-    public void setConfig(ZenModeConfig config, ComponentName triggeringComponent, String reason,
-            int callingUid, boolean fromSystemOrSystemUi) {
+    void setConfig(ZenModeConfig config, ComponentName triggeringComponent,
+            @ConfigChangeOrigin int origin, String reason, int callingUid) {
         synchronized (mConfigLock) {
-            setConfigLocked(config, triggeringComponent, reason, callingUid, fromSystemOrSystemUi);
+            setConfigLocked(config, triggeringComponent, origin, reason, callingUid);
         }
     }
 
     @GuardedBy("mConfigLock")
-    private boolean setConfigLocked(ZenModeConfig config, String reason,
-            ComponentName triggeringComponent, boolean setRingerMode, int callingUid,
-            boolean fromSystemOrSystemUi) {
+    private boolean setConfigLocked(ZenModeConfig config, @ConfigChangeOrigin int origin,
+            String reason, ComponentName triggeringComponent, boolean setRingerMode,
+            int callingUid) {
         final long identity = Binder.clearCallingIdentity();
         try {
             if (config == null || !config.isValid()) {
@@ -1332,8 +1315,7 @@
             if (policyChanged) {
                 dispatchOnPolicyChanged();
             }
-            updateConfigAndZenModeLocked(config, reason, setRingerMode, callingUid,
-                    fromSystemOrSystemUi, true);
+            updateConfigAndZenModeLocked(config, origin, reason, setRingerMode, callingUid);
             mConditions.evaluateConfig(config, triggeringComponent, true /*processSubscriptions*/);
             return true;
         } catch (SecurityException e) {
@@ -1349,17 +1331,16 @@
      * If logging is enabled, will also request logging of the outcome of this change if needed.
      */
     @GuardedBy("mConfigLock")
-    private void updateConfigAndZenModeLocked(ZenModeConfig config, String reason,
-            boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi,
-            boolean sendBroadcasts) {
+    private void updateConfigAndZenModeLocked(ZenModeConfig config, @ConfigChangeOrigin int origin,
+            String reason, boolean setRingerMode, int callingUid) {
         final boolean logZenModeEvents = mFlagResolver.isEnabled(
                 SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS);
         // Store (a copy of) all config and zen mode info prior to any changes taking effect
         ZenModeEventLogger.ZenModeInfo prevInfo = new ZenModeEventLogger.ZenModeInfo(
                 mZenMode, mConfig, mConsolidatedPolicy);
         if (!config.equals(mConfig)) {
-            // schedule broadcasts
-            if (Flags.modesApi() && sendBroadcasts) {
+            // Schedule broadcasts. Cannot be sent during boot, though.
+            if (Flags.modesApi() && origin != UPDATE_ORIGIN_INIT) {
                 for (ZenRule rule : config.automaticRules.values()) {
                     ZenRule original = mConfig.automaticRules.get(rule.id);
                     if (original != null) {
@@ -1377,15 +1358,19 @@
 
             mConfig = config;
             dispatchOnConfigChanged();
-            updateAndApplyConsolidatedPolicyAndDeviceEffects(reason);
+            updateAndApplyConsolidatedPolicyAndDeviceEffects(origin, reason);
         }
         final String val = Integer.toString(config.hashCode());
         Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
-        evaluateZenModeLocked(reason, setRingerMode);
+        evaluateZenModeLocked(origin, reason, setRingerMode);
         // After all changes have occurred, log if requested
         if (logZenModeEvents) {
             ZenModeEventLogger.ZenModeInfo newInfo = new ZenModeEventLogger.ZenModeInfo(
                     mZenMode, mConfig, mConsolidatedPolicy);
+            boolean fromSystemOrSystemUi = origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                    || origin == UPDATE_ORIGIN_INIT
+                    || origin == UPDATE_ORIGIN_INIT_USER
+                    || origin == UPDATE_ORIGIN_RESTORE_BACKUP;
             mZenModeEventLogger.maybeLogZenChange(prevInfo, newInfo, callingUid,
                     fromSystemOrSystemUi);
         }
@@ -1416,7 +1401,8 @@
 
     @VisibleForTesting
     @GuardedBy("mConfigLock")
-    protected void evaluateZenModeLocked(String reason, boolean setRingerMode) {
+    protected void evaluateZenModeLocked(@ConfigChangeOrigin int origin, String reason,
+            boolean setRingerMode) {
         if (DEBUG) Log.d(TAG, "evaluateZenMode");
         if (mConfig == null) return;
         final int policyHashBefore = mConsolidatedPolicy == null ? 0
@@ -1426,7 +1412,7 @@
         ZenLog.traceSetZenMode(zen, reason);
         mZenMode = zen;
         setZenModeSetting(mZenMode);
-        updateAndApplyConsolidatedPolicyAndDeviceEffects(reason);
+        updateAndApplyConsolidatedPolicyAndDeviceEffects(origin, reason);
         boolean shouldApplyToRinger = setRingerMode && (zen != zenBefore || (
                 zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
                         && policyHashBefore != mConsolidatedPolicy.hashCode()));
@@ -1468,6 +1454,7 @@
         }
     }
 
+    @GuardedBy("mConfigLock")
     private void applyCustomPolicy(ZenPolicy policy, ZenRule rule) {
         if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
             policy.apply(new ZenPolicy.Builder()
@@ -1487,7 +1474,9 @@
         }
     }
 
-    private void updateAndApplyConsolidatedPolicyAndDeviceEffects(String reason) {
+    @GuardedBy("mConfigLock")
+    private void updateAndApplyConsolidatedPolicyAndDeviceEffects(@ConfigChangeOrigin int origin,
+            String reason) {
         synchronized (mConfigLock) {
             if (mConfig == null) return;
             ZenPolicy policy = new ZenPolicy();
@@ -1519,13 +1508,13 @@
                 ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
                 if (!deviceEffects.equals(mConsolidatedDeviceEffects)) {
                     mConsolidatedDeviceEffects = deviceEffects;
-                    mHandler.postApplyDeviceEffects();
+                    mHandler.postApplyDeviceEffects(origin);
                 }
             }
         }
     }
 
-    private void applyConsolidatedDeviceEffects() {
+    private void applyConsolidatedDeviceEffects(@ConfigChangeOrigin int source) {
         if (!Flags.modesApi()) {
             return;
         }
@@ -1536,7 +1525,7 @@
             effects = mConsolidatedDeviceEffects;
         }
         if (applier != null) {
-            applier.apply(effects);
+            applier.apply(effects, source);
         }
     }
 
@@ -1801,8 +1790,8 @@
         }
 
         @Override
-        public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
-                int ringerModeExternal, VolumePolicy policy) {
+        public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew,
+                @Nullable String caller, int ringerModeExternal, VolumePolicy policy) {
             final boolean isChange = ringerModeOld != ringerModeNew;
 
             int ringerModeExternalOut = ringerModeNew;
@@ -1839,8 +1828,9 @@
             }
 
             if (newZen != -1) {
-                setManualZenMode(newZen, null, "ringerModeInternal", null,
-                        false /*setRingerMode*/, Process.SYSTEM_UID, true);
+                setManualZenMode(newZen, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                        "ringerModeInternal", /* caller= */ null, /* setRingerMode= */ false,
+                        Process.SYSTEM_UID);
             }
             if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
                 ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
@@ -1856,8 +1846,8 @@
         }
 
         @Override
-        public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
-                int ringerModeInternal, VolumePolicy policy) {
+        public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew,
+                @Nullable String caller, int ringerModeInternal, VolumePolicy policy) {
             int ringerModeInternalOut = ringerModeNew;
             final boolean isChange = ringerModeOld != ringerModeNew;
             final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
@@ -1883,8 +1873,8 @@
                     break;
             }
             if (newZen != -1) {
-                setManualZenMode(newZen, null, "ringerModeExternal", caller,
-                        false /*setRingerMode*/, Process.SYSTEM_UID, true);
+                setManualZenMode(newZen, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                        "ringerModeExternal", caller, false /*setRingerMode*/, Process.SYSTEM_UID);
             }
 
             ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller,
@@ -2024,11 +2014,9 @@
         }
         try {
             final Resources res = mPm.getResourcesForApplication(packageName);
-            final String fullName = res.getResourceName(resId);
-
-            return fullName;
+            return res.getResourceName(resId);
         } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
-            Log.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName
+            Slog.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName
                     + ". Resource IDs may change when the application is upgraded, and the system"
                     + " may not be able to find the correct resource.");
             return null;
@@ -2149,9 +2137,9 @@
             sendMessage(obtainMessage(MSG_RINGER_AUDIO, shouldApplyToRinger));
         }
 
-        private void postApplyDeviceEffects() {
+        private void postApplyDeviceEffects(@ConfigChangeOrigin int origin) {
             removeMessages(MSG_APPLY_EFFECTS);
-            sendEmptyMessage(MSG_APPLY_EFFECTS);
+            sendMessage(obtainMessage(MSG_APPLY_EFFECTS, origin, 0));
         }
 
         @Override
@@ -2168,7 +2156,8 @@
                     updateRingerAndAudio(shouldApplyToRinger);
                     break;
                 case MSG_APPLY_EFFECTS:
-                    applyConsolidatedDeviceEffects();
+                    @ConfigChangeOrigin int origin = msg.arg1;
+                    applyConsolidatedDeviceEffects(origin);
                     break;
             }
         }
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 92d469c..c36c8ca 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -405,9 +405,19 @@
     boolean isInstallDisabledForPackage(@NonNull String packageName, int uid,
             @UserIdInt int userId);
 
-    @Nullable
-    List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo,
-            @PackageManager.PackageInfoFlagsBits long flags, int callingUid, @UserIdInt int userId);
+    /**
+     * Returns a Pair that contains a list of packages that depend on the target library and the
+     * package library dependency information. The List&lt;VersionedPackage&gt; indicates a list of
+     * packages that depend on the target library, it may be null if no package depends on
+     * the target library. The List&lt;Boolean&gt; indicates whether each VersionedPackage in
+     * the List&lt;VersionedPackage&gt; optionally depends on the target library, where true means
+     * optional and false means required. It may be null if no package depends on
+     * the target library or without dependency information, e.g. uses-static-library.
+     */
+    @NonNull
+    Pair<List<VersionedPackage>, List<Boolean>> getPackagesUsingSharedLibrary(
+            @NonNull SharedLibraryInfo libInfo, @PackageManager.PackageInfoFlagsBits long flags,
+            int callingUid, @UserIdInt int userId);
 
     @Nullable
     ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries(
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 139c7c0..2ae1005 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -895,6 +895,9 @@
             @PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId) {
         ParsedActivity a = mComponentResolver.getActivity(component);
 
+        // Allow to match activities of quarantined packages.
+        flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;
+
         if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
 
         AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName());
@@ -3862,11 +3865,13 @@
                     Binder.restoreCallingIdentity(identity);
                 }
 
+                var usingSharedLibraryPair =
+                        getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId);
                 SharedLibraryInfo resLibInfo = new SharedLibraryInfo(libInfo.getPath(),
                         libInfo.getPackageName(), libInfo.getAllCodePaths(),
                         libInfo.getName(), libInfo.getLongVersion(),
                         libInfo.getType(), declaringPackage,
-                        getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId),
+                        usingSharedLibraryPair.first,
                         (libInfo.getDependencies() == null
                                 ? null
                                 : new ArrayList<>(libInfo.getDependencies())),
@@ -3935,13 +3940,15 @@
         return false;
     }
 
+
     @Override
-    public List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo,
-            @PackageManager.PackageInfoFlagsBits long flags, int callingUid,
-            @UserIdInt int userId) {
+    public Pair<List<VersionedPackage>, List<Boolean>> getPackagesUsingSharedLibrary(
+            @NonNull SharedLibraryInfo libInfo, @PackageManager.PackageInfoFlagsBits long flags,
+            int callingUid, @UserIdInt int userId) {
         List<VersionedPackage> versionedPackages = null;
         final ArrayMap<String, ? extends PackageStateInternal> packageStates = getPackageStates();
         final int packageCount = packageStates.size();
+        List<Boolean> usesLibsOptional = null;
         for (int i = 0; i < packageCount; i++) {
             PackageStateInternal ps = packageStates.valueAt(i);
             if (ps == null) {
@@ -3958,12 +3965,15 @@
                         libInfo.isStatic() ? ps.getUsesStaticLibraries() : ps.getUsesSdkLibraries();
                 final long[] libsVersions = libInfo.isStatic() ? ps.getUsesStaticLibrariesVersions()
                         : ps.getUsesSdkLibrariesVersionsMajor();
+                final boolean[] libsOptional = libInfo.isSdk()
+                        ? ps.getUsesSdkLibrariesOptional() : null;
 
                 final int libIdx = ArrayUtils.indexOf(libs, libName);
                 if (libIdx < 0) {
                     continue;
                 }
                 if (libsVersions[libIdx] != libInfo.getLongVersion()) {
+                    // Not expected StaticLib/SdkLib version
                     continue;
                 }
                 if (shouldFilterApplication(ps, callingUid, userId)) {
@@ -3972,6 +3982,9 @@
                 if (versionedPackages == null) {
                     versionedPackages = new ArrayList<>();
                 }
+                if (usesLibsOptional == null) {
+                    usesLibsOptional = new ArrayList<>();
+                }
                 // If the dependent is a static shared lib, use the public package name
                 String dependentPackageName = ps.getPackageName();
                 if (ps.getPkg() != null && ps.getPkg().isStaticSharedLibrary()) {
@@ -3979,6 +3992,7 @@
                 }
                 versionedPackages.add(new VersionedPackage(dependentPackageName,
                         ps.getVersionCode()));
+                usesLibsOptional.add(libsOptional != null && libsOptional[libIdx]);
             } else if (ps.getPkg() != null) {
                 if (ArrayUtils.contains(ps.getPkg().getUsesLibraries(), libName)
                         || ArrayUtils.contains(ps.getPkg().getUsesOptionalLibraries(), libName)) {
@@ -3994,7 +4008,7 @@
             }
         }
 
-        return versionedPackages;
+        return new Pair<>(versionedPackages, usesLibsOptional);
     }
 
     @Nullable
@@ -4053,13 +4067,14 @@
                     Binder.restoreCallingIdentity(identity);
                 }
 
+                var usingSharedLibraryPair =
+                        getPackagesUsingSharedLibrary(libraryInfo, flags, callingUid, userId);
                 SharedLibraryInfo resultLibraryInfo = new SharedLibraryInfo(
                         libraryInfo.getPath(), libraryInfo.getPackageName(),
                         libraryInfo.getAllCodePaths(), libraryInfo.getName(),
                         libraryInfo.getLongVersion(), libraryInfo.getType(),
                         libraryInfo.getDeclaringPackage(),
-                        getPackagesUsingSharedLibrary(
-                                libraryInfo, flags, callingUid, userId),
+                        usingSharedLibraryPair.first,
                         libraryInfo.getDependencies() == null
                                 ? null : new ArrayList<>(libraryInfo.getDependencies()),
                         libraryInfo.isNative());
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 80e6c83..93836266 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -18,7 +18,6 @@
 
 import static android.Manifest.permission.CONTROL_KEYGUARD;
 import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
-import static android.content.pm.Flags.sdkLibIndependence;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
@@ -40,6 +39,7 @@
 import android.app.ApplicationPackageManager;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.IPackageDeleteObserver2;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
@@ -72,8 +72,6 @@
 
 import dalvik.system.VMRuntime;
 
-import java.util.List;
-
 /**
  * Deletes a package. Uninstall if installed, or at least deletes the base directory if it's called
  * from a failed installation. Fixes user state after deletion.
@@ -181,16 +179,36 @@
                 }
 
                 if (libraryInfo != null) {
+                    boolean flagSdkLibIndependence = Flags.sdkLibIndependence();
                     for (int currUserId : allUsers) {
                         if (removeUser != UserHandle.USER_ALL && removeUser != currUserId) {
                             continue;
                         }
-                        List<VersionedPackage> libClientPackages =
-                                computer.getPackagesUsingSharedLibrary(libraryInfo,
-                                        MATCH_KNOWN_PACKAGES, Process.SYSTEM_UID, currUserId);
-                        boolean allowSdkLibIndependence =
-                                (pkg.getSdkLibraryName() != null) && sdkLibIndependence();
-                        if (!ArrayUtils.isEmpty(libClientPackages) && !allowSdkLibIndependence) {
+                        var libClientPackagesPair = computer.getPackagesUsingSharedLibrary(
+                                libraryInfo, MATCH_KNOWN_PACKAGES, Process.SYSTEM_UID, currUserId);
+                        var libClientPackages = libClientPackagesPair.first;
+                        var libClientOptional = libClientPackagesPair.second;
+                        // We by default don't allow removing a package if the host lib is still be
+                        // used by other client packages
+                        boolean allowLibIndependence = false;
+                        // Only when the sdkLibIndependence flag is enabled we will respect the
+                        // "optional" attr in uses-sdk-library. Only allow to remove sdk-lib host
+                        // package if no required clients depend on it
+                        if ((pkg.getSdkLibraryName() != null)
+                                && !ArrayUtils.isEmpty(libClientPackages)
+                                && !ArrayUtils.isEmpty(libClientOptional)
+                                && (libClientPackages.size() == libClientOptional.size())
+                                && flagSdkLibIndependence) {
+                            allowLibIndependence = true;
+                            for (int i = 0; i < libClientPackages.size(); i++) {
+                                boolean usesSdkLibOptional = libClientOptional.get(i);
+                                if (!usesSdkLibOptional) {
+                                    allowLibIndependence = false;
+                                    break;
+                                }
+                            }
+                        }
+                        if (!ArrayUtils.isEmpty(libClientPackages) && !allowLibIndependence) {
                             Slog.w(TAG, "Not removing package " + pkg.getManifestPackageName()
                                     + " hosting lib " + libraryInfo.getName() + " version "
                                     + libraryInfo.getLongVersion() + " used by " + libClientPackages
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f27c462..2880f84 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7113,9 +7113,9 @@
                         if (info == null) {
                             continue;
                         }
-                        final List<VersionedPackage> dependents =
-                                computer.getPackagesUsingSharedLibrary(info, 0, Process.SYSTEM_UID,
-                                        userId);
+                        var usingSharedLibraryPair = computer.getPackagesUsingSharedLibrary(info, 0,
+                                Process.SYSTEM_UID, userId);
+                        final List<VersionedPackage> dependents = usingSharedLibraryPair.first;
                         if (dependents == null) {
                             continue;
                         }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 174df44..7d0a1f6 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -123,11 +123,15 @@
     @Nullable
     private Map<String, Set<String>> mimeGroups;
 
+    // TODO(b/314036181): encapsulate all these fields for usesSdk, instead of having three
+    //  separate arrays.
     @Nullable
     private String[] usesSdkLibraries;
 
     @Nullable
     private long[] usesSdkLibrariesVersionsMajor;
+    @Nullable
+    private boolean[] usesSdkLibrariesOptional;
 
     @Nullable
     private String[] usesStaticLibraries;
@@ -701,6 +705,9 @@
         usesSdkLibrariesVersionsMajor = other.usesSdkLibrariesVersionsMajor != null
                 ? Arrays.copyOf(other.usesSdkLibrariesVersionsMajor,
                 other.usesSdkLibrariesVersionsMajor.length) : null;
+        usesSdkLibrariesOptional = other.usesSdkLibrariesOptional != null
+                ? Arrays.copyOf(other.usesSdkLibrariesOptional,
+                other.usesSdkLibrariesOptional.length) : null;
 
         usesStaticLibraries = other.usesStaticLibraries != null
                 ? Arrays.copyOf(other.usesStaticLibraries,
@@ -1344,6 +1351,12 @@
 
     @NonNull
     @Override
+    public boolean[] getUsesSdkLibrariesOptional() {
+        return usesSdkLibrariesOptional == null ? EmptyArray.BOOLEAN : usesSdkLibrariesOptional;
+    }
+
+    @NonNull
+    @Override
     public String[] getUsesStaticLibraries() {
         return usesStaticLibraries == null ? EmptyArray.STRING : usesStaticLibraries;
     }
@@ -1444,6 +1457,12 @@
         return this;
     }
 
+    public PackageSetting setUsesSdkLibrariesOptional(boolean[] usesSdkLibrariesOptional) {
+        this.usesSdkLibrariesOptional = usesSdkLibrariesOptional;
+        onChanged();
+        return this;
+    }
+
     public PackageSetting setUsesStaticLibraries(String[] usesStaticLibraries) {
         this.usesStaticLibraries = usesStaticLibraries;
         onChanged();
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 53b84e6..31a63e0 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -218,7 +218,8 @@
                     parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user,
                     true /*allowInstall*/, instantApp, virtualPreload, isStoppedSystemApp,
                     UserManagerService.getInstance(), usesSdkLibraries,
-                    parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
+                    parsedPackage.getUsesSdkLibrariesVersionsMajor(),
+                    parsedPackage.getUsesSdkLibrariesOptional(), usesStaticLibraries,
                     parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
                     newDomainSetId,
                     parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
@@ -240,6 +241,7 @@
                     PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting),
                     UserManagerService.getInstance(),
                     usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
+                    parsedPackage.getUsesSdkLibrariesOptional(),
                     usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
                     parsedPackage.getMimeGroups(), newDomainSetId,
                     parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 2cbf714..75d88da 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -106,6 +106,7 @@
 import com.android.server.backup.PreferredActivityBackupHelper;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.parsing.PackageInfoUtils;
+import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.permission.LegacyPermissionDataProvider;
 import com.android.server.pm.permission.LegacyPermissionSettings;
 import com.android.server.pm.permission.LegacyPermissionState;
@@ -339,6 +340,8 @@
     private static final String ATTR_DISTRACTION_FLAGS = "distraction_flags";
     private static final String ATTR_SUSPENDED = "suspended";
     private static final String ATTR_SUSPENDING_PACKAGE = "suspending-package";
+
+    private static final String ATTR_OPTIONAL = "optional";
     /**
      * @deprecated Legacy attribute, kept only for upgrading from P builds.
      */
@@ -942,6 +945,7 @@
             ret.setLongVersionCode(p.getVersionCode());
             ret.setUsesSdkLibraries(p.getUsesSdkLibraries());
             ret.setUsesSdkLibrariesVersionsMajor(p.getUsesSdkLibrariesVersionsMajor());
+            ret.setUsesSdkLibrariesOptional(p.getUsesSdkLibrariesOptional());
             ret.setUsesStaticLibraries(p.getUsesStaticLibraries());
             ret.setUsesStaticLibrariesVersions(p.getUsesStaticLibrariesVersions());
             ret.setMimeGroups(p.getMimeGroups());
@@ -1061,9 +1065,9 @@
             UserHandle installUser, boolean allowInstall, boolean instantApp,
             boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,
             String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
-            String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
-            Set<String> mimeGroupNames, @NonNull UUID domainSetId,
-            int targetSdkVersion, byte[] restrictUpdatedHash) {
+            boolean[] usesSdkLibrariesOptional, String[] usesStaticLibraries,
+            long[] usesStaticLibrariesVersions, Set<String> mimeGroupNames,
+            @NonNull UUID domainSetId, int targetSdkVersion, byte[] restrictUpdatedHash) {
         final PackageSetting pkgSetting;
         if (originalPkg != null) {
             if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
@@ -1079,6 +1083,7 @@
                     .setLongVersionCode(versionCode)
                     .setUsesSdkLibraries(usesSdkLibraries)
                     .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions)
+                    .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional)
                     .setUsesStaticLibraries(usesStaticLibraries)
                     .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
                     // Update new package state.
@@ -1096,6 +1101,7 @@
                     pkgPrivateFlags, domainSetId)
                     .setUsesSdkLibraries(usesSdkLibraries)
                     .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions)
+                    .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional)
                     .setUsesStaticLibraries(usesStaticLibraries)
                     .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
                     .setLegacyNativeLibraryPath(legacyNativeLibraryPath)
@@ -1218,6 +1224,7 @@
             @Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi, int pkgFlags,
             int pkgPrivateFlags, @NonNull UserManagerService userManager,
             @Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions,
+            @Nullable boolean[] usesSdkLibrariesOptional,
             @Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
             @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId,
             int targetSdkVersion, byte[] restrictUpdatedHash)
@@ -1277,12 +1284,17 @@
                 .setRestrictUpdateHash(restrictUpdatedHash);
         // Update SDK library dependencies if needed.
         if (usesSdkLibraries != null && usesSdkLibrariesVersions != null
-                && usesSdkLibraries.length == usesSdkLibrariesVersions.length) {
+                && usesSdkLibrariesOptional != null
+                && usesSdkLibraries.length == usesSdkLibrariesVersions.length
+                && usesSdkLibraries.length == usesSdkLibrariesOptional.length) {
             pkgSetting.setUsesSdkLibraries(usesSdkLibraries)
-                    .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions);
+                    .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions)
+                    .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional);
         } else {
             pkgSetting.setUsesSdkLibraries(null)
-                    .setUsesSdkLibrariesVersionsMajor(null);
+                    .setUsesSdkLibrariesVersionsMajor(null)
+                    .setUsesSdkLibrariesOptional(null);
+
         }
 
         // Update static shared library dependencies if needed.
@@ -2537,12 +2549,15 @@
             throws IOException, XmlPullParserException {
         String libName = parser.getAttributeValue(null, ATTR_NAME);
         long libVersion = parser.getAttributeLong(null, ATTR_VERSION, -1);
+        boolean optional = parser.getAttributeBoolean(null, ATTR_OPTIONAL, true);
 
         if (libName != null && libVersion >= 0) {
             outPs.setUsesSdkLibraries(ArrayUtils.appendElement(String.class,
                     outPs.getUsesSdkLibraries(), libName));
             outPs.setUsesSdkLibrariesVersionsMajor(ArrayUtils.appendLong(
                     outPs.getUsesSdkLibrariesVersionsMajor(), libVersion));
+            outPs.setUsesSdkLibrariesOptional(PackageImpl.appendBoolean(
+                    outPs.getUsesSdkLibrariesOptional(), optional));
         }
 
         XmlUtils.skipCurrentTag(parser);
@@ -2564,7 +2579,7 @@
     }
 
     void writeUsesSdkLibLPw(TypedXmlSerializer serializer, String[] usesSdkLibraries,
-            long[] usesSdkLibraryVersions) throws IOException {
+            long[] usesSdkLibraryVersions, boolean[] usesSdkLibrariesOptional) throws IOException {
         if (ArrayUtils.isEmpty(usesSdkLibraries) || ArrayUtils.isEmpty(usesSdkLibraryVersions)
                 || usesSdkLibraries.length != usesSdkLibraryVersions.length) {
             return;
@@ -2573,9 +2588,11 @@
         for (int i = 0; i < libCount; i++) {
             final String libName = usesSdkLibraries[i];
             final long libVersion = usesSdkLibraryVersions[i];
+            boolean libOptional = usesSdkLibrariesOptional[i];
             serializer.startTag(null, TAG_USES_SDK_LIB);
             serializer.attribute(null, ATTR_NAME, libName);
             serializer.attributeLong(null, ATTR_VERSION, libVersion);
+            serializer.attributeBoolean(null, ATTR_OPTIONAL, libOptional);
             serializer.endTag(null, TAG_USES_SDK_LIB);
         }
     }
@@ -3106,7 +3123,8 @@
         }
 
         writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
-                pkg.getUsesSdkLibrariesVersionsMajor());
+                pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional());
+
 
         writeUsesStaticLibLPw(serializer, pkg.getUsesStaticLibraries(),
                 pkg.getUsesStaticLibrariesVersions());
@@ -3206,7 +3224,7 @@
         }
 
         writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
-                pkg.getUsesSdkLibrariesVersionsMajor());
+                pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional());
 
         writeUsesStaticLibLPw(serializer, pkg.getUsesStaticLibraries(),
                 pkg.getUsesStaticLibrariesVersions());
@@ -5091,12 +5109,14 @@
 
             List<String> usesSdkLibraries = pkg.getUsesSdkLibraries();
             long[] usesSdkLibrariesVersionsMajor = pkg.getUsesSdkLibrariesVersionsMajor();
+            boolean[] usesSdkLibrariesOptional = pkg.getUsesSdkLibrariesOptional();
             if (usesSdkLibraries.size() > 0) {
                 pw.print(prefix); pw.println("  usesSdkLibraries:");
                 for (int i = 0, size = usesSdkLibraries.size(); i < size; ++i) {
                     pw.print(prefix); pw.print("    ");
                     pw.print(usesSdkLibraries.get(i)); pw.print(" version:");
-                    pw.println(usesSdkLibrariesVersionsMajor[i]);
+                    pw.println(usesSdkLibrariesVersionsMajor[i]); pw.print(" optional:");
+                    pw.println(usesSdkLibrariesOptional[i]);
                 }
             }
 
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 9384c13..94495bf 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.server.pm;
 
-import static android.content.pm.Flags.sdkLibIndependence;
 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST;
 
@@ -28,6 +27,7 @@
 import android.annotation.Nullable;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
+import android.content.pm.Flags;
 import android.content.pm.PackageManager;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.Signature;
@@ -82,6 +82,8 @@
 public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable, Snappable {
     private static final boolean DEBUG_SHARED_LIBRARIES = false;
 
+    private static final String LIBRARY_TYPE_SDK = "sdk";
+
     /**
      * Apps targeting Android S and above need to declare dependencies to the public native
      * shared libraries that are defined by the device maker using {@code uses-native-library} tag
@@ -798,8 +800,9 @@
 
             // Remove the shared library overlays from its dependent packages.
             for (int currentUserId : mPm.mUserManager.getUserIds()) {
-                final List<VersionedPackage> dependents = snapshot.getPackagesUsingSharedLibrary(
-                        libraryInfo, 0, Process.SYSTEM_UID, currentUserId);
+                var usingSharedLibraryPair = snapshot.getPackagesUsingSharedLibrary(libraryInfo, 0,
+                        Process.SYSTEM_UID, currentUserId);
+                final List<VersionedPackage> dependents = usingSharedLibraryPair.first;
                 if (dependents == null) {
                     continue;
                 }
@@ -921,42 +924,43 @@
         // duplicates.
         ArrayList<SharedLibraryInfo> usesLibraryInfos = null;
         if (!pkg.getUsesLibraries().isEmpty()) {
-            usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null,
+            usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, null,
                     pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null,
                     availablePackages, newLibraries);
         }
         if (!pkg.getUsesStaticLibraries().isEmpty()) {
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
                     pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
-                    pkg.getPackageName(), "static shared", true, pkg.getTargetSdkVersion(),
-                    usesLibraryInfos, availablePackages, newLibraries);
+                    null, pkg.getPackageName(), "static shared", true,
+                    pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries);
         }
         if (!pkg.getUsesOptionalLibraries().isEmpty()) {
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null,
-                    pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
+                    null, pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
                     usesLibraryInfos, availablePackages, newLibraries);
         }
         if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
                 pkg.getPackageName(), pkg.getTargetSdkVersion())) {
             if (!pkg.getUsesNativeLibraries().isEmpty()) {
                 usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
-                        null, pkg.getPackageName(), "native shared", true,
+                        null, null, pkg.getPackageName(), "native shared", true,
                         pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
                         newLibraries);
             }
             if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
                 usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
-                        null, null, pkg.getPackageName(), "native shared", false,
+                        null, null, null, pkg.getPackageName(), "native shared", false,
                         pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
                         newLibraries);
             }
         }
         if (!pkg.getUsesSdkLibraries().isEmpty()) {
             // Allow installation even if sdk-library dependency doesn't exist
-            boolean required = !sdkLibIndependence();
+            boolean required = !Flags.sdkLibIndependence();
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(),
                     pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
-                    pkg.getPackageName(), "sdk", required, pkg.getTargetSdkVersion(),
+                    pkg.getUsesSdkLibrariesOptional(),
+                    pkg.getPackageName(), LIBRARY_TYPE_SDK, required, pkg.getTargetSdkVersion(),
                     usesLibraryInfos, availablePackages, newLibraries);
         }
         return usesLibraryInfos;
@@ -965,6 +969,7 @@
     private ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
             @NonNull List<String> requestedLibraries,
             @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
+            @Nullable boolean[] libsOptional,
             @NonNull String packageName, @NonNull String libraryType, boolean required,
             int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
             @NonNull final Map<String, AndroidPackage> availablePackages,
@@ -981,7 +986,10 @@
                         libName, libVersion, mSharedLibraries, newLibraries);
             }
             if (libraryInfo == null) {
-                if (required) {
+                // Only allow app be installed if the app specifies the sdk-library dependency is
+                // optional
+                if (required || (LIBRARY_TYPE_SDK.equals(libraryType) && (libsOptional != null
+                        && !libsOptional[i]))) {
                     throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
                             "Package " + packageName + " requires unavailable " + libraryType
                                     + " library " + libName + "; failing!");
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 71f6c0d..fe8c12c 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -33,7 +33,6 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.DeviceConfig;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IntArray;
@@ -504,10 +503,6 @@
             final String requiredPermissionControllerPackage =
                     getKnownPackageName(snapshot, KnownPackages.PACKAGE_PERMISSION_CONTROLLER,
                             userId);
-            final AppOpsManager appOpsManager = mInjector.getSystemService(AppOpsManager.class);
-            final boolean isSystemExemptFlagEnabled = DeviceConfig.getBoolean(
-                    DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
-                    SYSTEM_EXEMPT_FROM_SUSPENSION, /* defaultValue= */ true);
             for (int i = 0; i < packageNames.length; i++) {
                 canSuspend[i] = false;
                 final String packageName = packageNames[i];
@@ -581,9 +576,7 @@
                                 + pkg.getStaticSharedLibraryName());
                         continue;
                     }
-                    if (isSystemExemptFlagEnabled && appOpsManager.checkOpNoThrow(
-                            AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION, uid, packageName)
-                            == AppOpsManager.MODE_ALLOWED) {
+                    if (exemptFromSuspensionByAppOp(uid, packageName)) {
                         Slog.w(TAG, "Cannot suspend package \"" + packageName
                                 + "\": has OP_SYSTEM_EXEMPT_FROM_SUSPENSION set");
                         continue;
@@ -601,6 +594,13 @@
         return canSuspend;
     }
 
+    private boolean exemptFromSuspensionByAppOp(int uid, String packageName) {
+        final AppOpsManager appOpsManager = mInjector.getSystemService(AppOpsManager.class);
+        return appOpsManager.checkOpNoThrow(
+                AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION, uid, packageName)
+                        == AppOpsManager.MODE_ALLOWED;
+    }
+
     /**
      * Suspends packages on behalf of an admin.
      *
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b53a21c..a7b52f4 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1519,7 +1519,7 @@
 
         try {
             if (enableQuietMode) {
-                ActivityManager.getService().stopUser(userId, /* force= */ true, null);
+                stopUserForQuietMode(userId);
                 LocalServices.getService(ActivityManagerInternal.class)
                         .killForegroundAppsForUser(userId);
             } else {
@@ -1547,6 +1547,18 @@
         }
     }
 
+    private void stopUserForQuietMode(int userId) throws RemoteException {
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+            // Allow delayed locking since some profile types want to be able to unlock again via
+            // biometrics.
+            ActivityManager.getService()
+                    .stopUserWithDelayedLocking(userId, /* force= */ true, null);
+            return;
+        }
+        ActivityManager.getService().stopUser(userId, /* force= */ true, null);
+    }
+
     private void logQuietModeEnabled(@UserIdInt int userId, boolean enableQuietMode,
             @Nullable String callingPackage) {
         Slogf.i(LOG_TAG,
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 7f013b8..7386301 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -311,6 +311,7 @@
                         .setStartWithParent(true)
                         .setCredentialShareableWithParent(true)
                         .setAuthAlwaysRequiredToDisableQuietMode(true)
+                        .setAllowStoppingUserWithDelayedLocking(true)
                         .setMediaSharedWithParent(false)
                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
                         .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d642018..b23dbee 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -266,17 +266,20 @@
         if ((flags & PackageManager.GET_ACTIVITIES) != 0) {
             final int N = pkg.getActivities().size();
             if (N > 0) {
+                // Allow to match activities of quarantined packages.
+                long aflags = flags | PackageManager.MATCH_QUARANTINED_COMPONENTS;
+
                 int num = 0;
                 final ActivityInfo[] res = new ActivityInfo[N];
                 for (int i = 0; i < N; i++) {
                     final ParsedActivity a = pkg.getActivities().get(i);
                     if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), a,
-                            flags)) {
+                            aflags)) {
                         if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(
                                 a.getName())) {
                             continue;
                         }
-                        res[num++] = generateActivityInfo(pkg, a, flags, state,
+                        res[num++] = generateActivityInfo(pkg, a, aflags, state,
                                 applicationInfo, userId, pkgSetting);
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index 85d95ea..da58d47 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -267,6 +267,8 @@
     @Nullable
     private String[][] usesSdkLibrariesCertDigests;
     @Nullable
+    private boolean[] usesSdkLibrariesOptional;
+    @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
     private String sharedUserId;
     private int sharedUserLabel;
@@ -718,16 +720,33 @@
 
     @Override
     public PackageImpl addUsesSdkLibrary(String libraryName, long versionMajor,
-            String[] certSha256Digests) {
+            String[] certSha256Digests, boolean usesSdkLibrariesOptional) {
         this.usesSdkLibraries = CollectionUtils.add(this.usesSdkLibraries,
                 TextUtils.safeIntern(libraryName));
         this.usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong(
                 this.usesSdkLibrariesVersionsMajor, versionMajor, true);
         this.usesSdkLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
                 this.usesSdkLibrariesCertDigests, certSha256Digests, true);
+        this.usesSdkLibrariesOptional = appendBoolean(this.usesSdkLibrariesOptional,
+                usesSdkLibrariesOptional);
         return this;
     }
 
+    /**
+     * Adds value to given array if not already present, providing set-like
+     * behavior.
+     */
+    public static boolean[] appendBoolean(@Nullable boolean[] cur, boolean val) {
+        if (cur == null) {
+            return new boolean[] { val };
+        }
+        final int N = cur.length;
+        boolean[] ret = new boolean[N + 1];
+        System.arraycopy(cur, 0, ret, 0, N);
+        ret[N] = val;
+        return ret;
+    }
+
     @Override
     public PackageImpl addUsesStaticLibrary(String libraryName, long version,
             String[] certSha256Digests) {
@@ -1468,6 +1487,12 @@
     @Override
     public long[] getUsesSdkLibrariesVersionsMajor() { return usesSdkLibrariesVersionsMajor; }
 
+    @Nullable
+    @Override
+    public boolean[] getUsesSdkLibrariesOptional() {
+        return usesSdkLibrariesOptional;
+    }
+
     @NonNull
     @Override
     public List<String> getUsesStaticLibraries() {
@@ -3126,6 +3151,7 @@
                 dest.writeStringArray(this.usesSdkLibrariesCertDigests[index]);
             }
         }
+        dest.writeBooleanArray(this.usesSdkLibrariesOptional);
 
         sForInternedString.parcel(this.sharedUserId, dest, flags);
         dest.writeInt(this.sharedUserLabel);
@@ -3278,6 +3304,7 @@
                 }
             }
         }
+        this.usesSdkLibrariesOptional = in.createBooleanArray();
 
         this.sharedUserId = sForInternedString.unparcel(in);
         this.sharedUserLabel = in.readInt();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 10b59c7..a7ae4eb 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -322,6 +322,14 @@
     long[] getUsesSdkLibrariesVersionsMajor();
 
     /**
+     * @see R.styleable#AndroidManifestUsesSdkLibrary_optional
+     * @hide
+     */
+    @Immutable.Ignore
+    @NonNull
+    boolean[] getUsesSdkLibrariesOptional();
+
+    /**
      * @see R.styleable#AndroidManifestUsesStaticLibrary
      * @hide
      */
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 722350a..aa0fb27 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -2598,6 +2598,8 @@
                     R.styleable.AndroidManifestUsesSdkLibrary_versionMajor, -1);
             String certSha256Digest = sa.getNonResourceString(R.styleable
                     .AndroidManifestUsesSdkLibrary_certDigest);
+            boolean optional =
+                    sa.getBoolean(R.styleable.AndroidManifestUsesSdkLibrary_optional, false);
 
             // Since an APK providing a static shared lib can only provide the lib - fail if
             // malformed
@@ -2641,7 +2643,8 @@
             System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
                     1, additionalCertSha256Digests.length);
 
-            return input.success(pkg.addUsesSdkLibrary(lname, versionMajor, certSha256Digests));
+            return input.success(
+                    pkg.addUsesSdkLibrary(lname, versionMajor, certSha256Digests, optional));
         } finally {
             sa.recycle();
         }
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 99653ae..24d7acd 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -759,6 +759,36 @@
                     case "NPU":
                         type = Temperature.TYPE_NPU;
                         break;
+                    case "TPU":
+                        type = Temperature.TYPE_TPU;
+                        break;
+                    case "DISPLAY":
+                        type = Temperature.TYPE_DISPLAY;
+                        break;
+                    case "MODEM":
+                        type = Temperature.TYPE_MODEM;
+                        break;
+                    case "SOC":
+                        type = Temperature.TYPE_SOC;
+                        break;
+                    case "WIFI":
+                        type = Temperature.TYPE_WIFI;
+                        break;
+                    case "CAMERA":
+                        type = Temperature.TYPE_CAMERA;
+                        break;
+                    case "FLASHLIGHT":
+                        type = Temperature.TYPE_FLASHLIGHT;
+                        break;
+                    case "SPEAKER":
+                        type = Temperature.TYPE_SPEAKER;
+                        break;
+                    case "AMBIENT":
+                        type = Temperature.TYPE_AMBIENT;
+                        break;
+                    case "POGO":
+                        type = Temperature.TYPE_POGO;
+                        break;
                     default:
                         pw.println("Invalid temperature type: " + typeName);
                         return -1;
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
index 43fd15d..6fbbc0f 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.os.BatteryConsumer;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerStats;
 
 import java.lang.annotation.Retention;
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
index 5fd8ddf..7feb964 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.util.Log;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerStats;
 
 import java.util.ArrayList;
diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/services/core/java/com/android/server/power/stats/MultiStateStats.java
similarity index 99%
rename from core/java/com/android/internal/os/MultiStateStats.java
rename to services/core/java/com/android/server/power/stats/MultiStateStats.java
index ecfed53..9356950 100644
--- a/core/java/com/android/internal/os/MultiStateStats.java
+++ b/services/core/java/com/android/server/power/stats/MultiStateStats.java
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.internal.os;
+package com.android.server.power.stats;
 
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.LongArrayMultiStateCounter;
 import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 0facb9c..1637022 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -21,7 +21,6 @@
 import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerStats;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 1f6f113..c267b79 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -22,7 +22,6 @@
 import android.os.UidBatteryConsumer;
 import android.util.Slog;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerStats;
 
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e4c7fc1..6f27507 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -1851,14 +1851,14 @@
                             sessionState.currentChannel = channelUri;
                             notifyCurrentChannelInfosUpdatedLocked(userState);
                             if (!sessionState.isRecordingSession) {
-                                String actualInputId = getActualInputId(sessionState);
-                                if (!TextUtils.equals(mOnScreenInputId, actualInputId)) {
+                                String sessionActualInputId = getSessionActualInputId(sessionState);
+                                if (!TextUtils.equals(mOnScreenInputId, sessionActualInputId)) {
                                     logExternalInputEvent(
                                             FrameworkStatsLog
                                                     .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
-                                            actualInputId, sessionState);
+                                            sessionActualInputId, sessionState);
                                 }
-                                mOnScreenInputId = actualInputId;
+                                mOnScreenInputId = sessionActualInputId;
                                 mOnScreenSessionState = sessionState;
                             }
                         }
@@ -2985,11 +2985,20 @@
     // e.g. if an HDMI port has a CEC device plugged in, the actual input id of the HDMI
     // session should be the input id of CEC device instead of the default HDMI input id.
     @GuardedBy("mLock")
-    private String getActualInputId(SessionState sessionState) {
+    private String getSessionActualInputId(SessionState sessionState) {
         UserState userState = getOrCreateUserStateLocked(sessionState.userId);
         TvInputState tvInputState = userState.inputMap.get(sessionState.inputId);
+        if (tvInputState == null) {
+            Slog.w(TAG, "No TvInputState for sessionState.inputId " + sessionState.inputId);
+            return sessionState.inputId;
+        }
         TvInputInfo tvInputInfo = tvInputState.info;
-        String actualInputId = sessionState.inputId;
+        if (tvInputInfo == null) {
+            Slog.w(TAG, "TvInputInfo is null for input id " + sessionState.inputId);
+            return sessionState.inputId;
+        }
+
+        String sessionActualInputId = sessionState.inputId;
         switch (tvInputInfo.getType()) {
             case TvInputInfo.TYPE_HDMI:
                 // TODO: find a better approach towards active CEC device in future
@@ -2997,13 +3006,13 @@
                         mTvInputHardwareManager.getHdmiParentInputMap();
                 if (hdmiParentInputMap.containsKey(sessionState.inputId)) {
                     List<String> parentInputList = hdmiParentInputMap.get(sessionState.inputId);
-                    actualInputId = parentInputList.get(0);
+                    sessionActualInputId = parentInputList.get(0);
                 }
                 break;
             default:
                 break;
         }
-        return actualInputId;
+        return sessionActualInputId;
     }
 
     @Nullable
@@ -3111,7 +3120,21 @@
     private void logExternalInputEvent(int eventType, String inputId, SessionState sessionState) {
         UserState userState = getOrCreateUserStateLocked(sessionState.userId);
         TvInputState tvInputState = userState.inputMap.get(inputId);
+        if (tvInputState == null) {
+            Slog.w(TAG, "Cannot find input state for input id " + inputId);
+            // If input id is not found, try to find the input id of this sessionState.
+            inputId = sessionState.inputId;
+            tvInputState = userState.inputMap.get(inputId);
+        }
+        if (tvInputState == null) {
+            Slog.w(TAG, "Cannot find input state for sessionState.inputId " + inputId);
+            return;
+        }
         TvInputInfo tvInputInfo = tvInputState.info;
+        if (tvInputInfo == null) {
+            Slog.w(TAG, "TvInputInfo is null for input id " + inputId);
+            return;
+        }
         int inputState = tvInputState.state;
         int inputType = tvInputInfo.getType();
         String displayName = tvInputInfo.loadLabel(mContext).toString();
@@ -3647,14 +3670,14 @@
                         mSessionState.currentChannel = channelUri;
                         notifyCurrentChannelInfosUpdatedLocked(userState);
                         if (!mSessionState.isRecordingSession) {
-                            String actualInputId = getActualInputId(mSessionState);
-                            if (!TextUtils.equals(mOnScreenInputId, actualInputId)) {
+                            String sessionActualInputId = getSessionActualInputId(mSessionState);
+                            if (!TextUtils.equals(mOnScreenInputId, sessionActualInputId)) {
                                 logExternalInputEvent(
                                         FrameworkStatsLog
                                                 .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
-                                        actualInputId, mSessionState);
+                                        sessionActualInputId, mSessionState);
                             }
-                            mOnScreenInputId = actualInputId;
+                            mOnScreenInputId = sessionActualInputId;
                             mOnScreenSessionState = mSessionState;
                         }
                     }
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index 2b6dffb..7b5192c 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -16,37 +16,22 @@
 
 package com.android.server.utils;
 
-import static android.text.TextUtils.formatSimple;
-
-import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.Message;
-import android.os.Process;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.text.TextUtils;
 import android.text.format.TimeMigrationUtils;
-import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
-import android.util.MathUtils;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.Keep;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.util.RingBuffer;
 
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Objects;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This class managers AnrTimers.  An AnrTimer is a substitute for a delayed Message.  In legacy
@@ -102,12 +87,6 @@
     private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
     /**
-     * Enable tracing from the time a timer expires until it is accepted or discarded.  This is
-     * used to diagnose long latencies in the client.
-     */
-    private static final boolean ENABLE_TRACING = false;
-
-    /**
      * Return true if the feature is enabled.  By default, the value is take from the Flags class
      * but it can be changed for local testing.
      */
@@ -116,22 +95,13 @@
     }
 
     /**
-     * The status of an ANR timer.  TIMER_INVALID status is returned when an error is detected.
+     * This class allows test code to provide instance-specific overrides.
      */
-    private static final int TIMER_INVALID = 0;
-    private static final int TIMER_RUNNING = 1;
-    private static final int TIMER_EXPIRED = 2;
-
-    @IntDef(prefix = { "TIMER_" }, value = {
-                TIMER_INVALID, TIMER_RUNNING, TIMER_EXPIRED
-            })
-    private @interface TimerStatus {}
-
-    /**
-     * A static list of all known AnrTimer instances, used for dumping and testing.
-     */
-    @GuardedBy("sAnrTimerList")
-    private static final ArrayList<WeakReference<AnrTimer>> sAnrTimerList = new ArrayList<>();
+    static class Injector {
+        boolean anrTimerServiceEnabled() {
+            return AnrTimer.anrTimerServiceEnabled();
+        }
+    }
 
     /**
      * An error is defined by its issue, the operation that detected the error, the tag of the
@@ -161,6 +131,22 @@
             this.arg = arg;
             this.timestamp = SystemClock.elapsedRealtime();
         }
+
+        /**
+         * Dump a single error to the output stream.
+         */
+        private void dump(IndentingPrintWriter ipw, int seq) {
+            ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, operation, tag, issue, arg);
+
+            final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime();
+            final long etime = offset + timestamp;
+            ipw.println("    date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime));
+            ipw.increaseIndent();
+            for (int i = 0; i < stack.length; i++) {
+                ipw.println("    " + stack[i].toString());
+            }
+            ipw.decreaseIndent();
+        }
     }
 
     /**
@@ -171,132 +157,10 @@
     @GuardedBy("sErrors")
     private static final RingBuffer<Error> sErrors = new RingBuffer<>(Error.class, 20);
 
-    /**
-     * A record of a single anr timer.  The pid and uid are retained for reference but they do not
-     * participate in the equality tests. A {@link Timer} is bound to its parent {@link AnrTimer}
-     * through the owner field.  Access to timer fields is guarded by the mLock of the owner.
-     */
-    private static class Timer {
-        /** The AnrTimer that is managing this Timer. */
-        final AnrTimer owner;
-
-        /** The argument that uniquely identifies the Timer in the context of its current owner. */
-        final Object arg;
-        /** The pid of the process being tracked by this Timer. */
-        final int pid;
-        /** The uid of the process being tracked by this Timer as reported by the kernel. */
-        final int uid;
-        /** The original timeout. */
-        final long timeoutMs;
-
-        /** The status of the Timer.  */
-        @GuardedBy("owner.mLock")
-        @TimerStatus
-        int status;
-
-        /** The absolute time the timer was startd */
-        final long startedMs;
-
-        /** Fields used by the native timer service. */
-
-        /** The timer ID: used to exchange information with the native service. */
-        int timerId;
-
-        /** Fields used by the legacy timer service. */
-
-        /**
-         * The process's cpu delay time when the timer starts . It is meaningful only if
-         * extendable is true.  The cpu delay is cumulative, so the incremental delay that occurs
-         * during a timer is the delay at the end of the timer minus this value.  Units are in
-         * milliseconds.
-         */
-        @GuardedBy("owner.mLock")
-        long initialCpuDelayMs;
-
-        /** True if the timer has been extended. */
-        @GuardedBy("owner.mLock")
-        boolean extended;
-
-        /**
-         * Fetch a new Timer.  This is private.  Clients should get a new timer using the obtain()
-         * method.
-         */
-        private Timer(int pid, int uid, @Nullable Object arg, long timeoutMs,
-                @NonNull AnrTimer service) {
-            this.arg = arg;
-            this.pid = pid;
-            this.uid = uid;
-            this.timerId = 0;
-            this.timeoutMs = timeoutMs;
-            this.startedMs = now();
-            this.owner = service;
-            this.initialCpuDelayMs = 0;
-            this.extended = false;
-            this.status = TIMER_INVALID;
-        }
-
-        /** Get a timer.  This implementation constructs a new timer. */
-        static Timer obtain(int pid, int uid, @Nullable Object arg, long timeout,
-                @NonNull AnrTimer service) {
-            return new Timer(pid, uid, arg, timeout, service);
-        }
-
-        /** Release a timer. This implementation simply drops the timer. */
-        void release() {
-        }
-
-        /** Return the age of the timer. This is used for debugging. */
-        long age() {
-            return now() - startedMs;
-        }
-
-        /**
-         * The hash code is generated from the owner and the argument.  By definition, the
-         * combination must be unique for the lifetime of an in-use Timer.
-         */
-        @Override
-        public int hashCode() {
-            return Objects.hash(owner, arg);
-        }
-
-        /**
-         * The equality check compares the owner and the argument.  By definition, the combination
-         * must be unique for the lifetime of an in-use Timer.
-         */
-        @Override
-        public boolean equals(Object r) {
-            if (r instanceof Timer) {
-                Timer t = (Timer) r;
-                return Objects.equals(owner, t.owner) && Objects.equals(arg, t.arg);
-            }
-            return false;
-        }
-
-        @Override
-        public String toString() {
-            final int myStatus;
-            synchronized (owner.mLock) {
-                myStatus = status;
-            }
-            return "timerId=" + timerId + " pid=" + pid + " uid=" + uid
-                    + " " + statusString(myStatus) + " " + owner.mLabel;
-        }
-    }
-
     /** A lock for the AnrTimer instance. */
     private final Object mLock = new Object();
 
     /**
-     * The map from client argument to the associated timer.
-     */
-    @GuardedBy("mLock")
-    private final ArrayMap<V, Timer> mTimerMap = new ArrayMap<>();
-
-    /** The highwater mark of started, but not closed, timers. */
-    @GuardedBy("mLock")
-    private int mMaxStarted = 0;
-
-    /**
      * The total number of timers started.
      */
     @GuardedBy("mLock")
@@ -309,176 +173,6 @@
     private int mTotalErrors = 0;
 
     /**
-     * The total number of timers that have expired.
-     */
-    @GuardedBy("mLock")
-    private int mTotalExpired = 0;
-
-    /**
-     * A TimerService that generates a timeout event <n> milliseconds in the future.  See the
-     * class documentation for an explanation of the operations.
-     */
-    private abstract class TimerService {
-        /** Start a timer.  The timeout must be initialized. */
-        abstract boolean start(@NonNull Timer timer);
-
-        abstract void cancel(@NonNull Timer timer);
-
-        abstract void accept(@NonNull Timer timer);
-
-        abstract void discard(@NonNull Timer timer);
-    }
-
-    /**
-     * A class to assist testing.  All methods are null by default but can be overridden as
-     * necessary for a test.
-     */
-    @VisibleForTesting
-    static class Injector {
-        private final Handler mReferenceHandler;
-
-        Injector(@NonNull Handler handler) {
-            mReferenceHandler = handler;
-        }
-
-        /**
-         * Return a handler for the given Callback, based on the reference handler. The handler
-         * might be mocked, in which case it does not have a valid Looper.  In this case, use the
-         * main Looper.
-         */
-        @NonNull
-        Handler newHandler(@NonNull Handler.Callback callback) {
-            Looper looper = mReferenceHandler.getLooper();
-            if (looper == null) looper = Looper.getMainLooper();
-            return new Handler(looper, callback);
-        }
-
-        /**
-         * Return a CpuTracker. The default behavior is to create a new CpuTracker but this changes
-         * for unit tests.
-         **/
-        @NonNull
-        CpuTracker newTracker() {
-            return new CpuTracker();
-        }
-
-        /** Return true if the feature is enabled. */
-        boolean isFeatureEnabled() {
-            return anrTimerServiceEnabled();
-        }
-    }
-
-    /**
-     * A helper class to measure CPU delays.  Given a process ID, this class will return the
-     * cumulative CPU delay for the PID, since process inception.  This class is defined to assist
-     * testing.
-     */
-    @VisibleForTesting
-    static class CpuTracker {
-        /**
-         * The parameter to ProcessCpuTracker indicates that statistics should be collected on a
-         * single process and not on the collection of threads associated with that process.
-         */
-        private final ProcessCpuTracker mCpu = new ProcessCpuTracker(false);
-
-        /** A simple wrapper to fetch the delay.  This method can be overridden for testing. */
-        long delay(int pid) {
-            return mCpu.getCpuDelayTimeForPid(pid);
-        }
-    }
-
-    /**
-     * The "user-space" implementation of the timer service.  This service uses its own message
-     * handler to create timeouts.
-     */
-    private class HandlerTimerService extends TimerService {
-        /** The lock for this handler */
-        private final Object mLock = new Object();
-
-        /** The message handler for scheduling future events. */
-        private final Handler mHandler;
-
-        /** The interface to fetch process statistics that might extend an ANR timeout. */
-        private final CpuTracker mCpu;
-
-        /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */
-        @VisibleForTesting
-        HandlerTimerService(@NonNull Injector injector) {
-            mHandler = injector.newHandler(this::expires);
-            mCpu = injector.newTracker();
-        }
-
-        /** Post a message with the specified timeout.  The timer is not modified. */
-        private void post(@NonNull Timer t, long timeoutMillis) {
-            final Message msg = mHandler.obtainMessage();
-            msg.obj = t;
-            mHandler.sendMessageDelayed(msg, timeoutMillis);
-        }
-
-        /**
-         * The local expiration handler first attempts to compute a timer extension.  If the timer
-         * should be extended, it is rescheduled in the future (granting more time to the
-         * associated process).  If the timer should not be extended then the timeout is delivered
-         * to the client.
-         *
-         * A process is extended to account for the time the process was swapped out and was not
-         * runnable through no fault of its own.  A timer can only be extended once and only if
-         * the AnrTimer permits extensions.  Finally, a timer will never be extended by more than
-         * the original timeout, so the total timeout will never be more than twice the originally
-         * configured timeout.
-         */
-        private boolean expires(Message msg) {
-            Timer t = (Timer) msg.obj;
-            synchronized (mLock) {
-                long extension = 0;
-                if (mExtend && !t.extended) {
-                    extension = mCpu.delay(t.pid) - t.initialCpuDelayMs;
-                    if (extension < 0) extension = 0;
-                    if (extension > t.timeoutMs) extension = t.timeoutMs;
-                    t.extended = true;
-                }
-                if (extension > 0) {
-                    post(t, extension);
-                } else {
-                    onExpiredLocked(t);
-                }
-            }
-            return true;
-        }
-
-        @GuardedBy("mLock")
-        @Override
-        boolean start(@NonNull Timer t) {
-            if (mExtend) {
-                t.initialCpuDelayMs = mCpu.delay(t.pid);
-            }
-            post(t, t.timeoutMs);
-            return true;
-        }
-
-        @Override
-        void cancel(@NonNull Timer t) {
-            mHandler.removeMessages(0, t);
-        }
-
-        @Override
-        void accept(@NonNull Timer t) {
-            // Nothing to do.
-        }
-
-        @Override
-        void discard(@NonNull Timer t) {
-            // Nothing to do.
-        }
-
-        /** The string identifies this subclass of AnrTimerService as being based on handlers. */
-        @Override
-        public String toString() {
-            return "handler";
-        }
-    }
-
-    /**
      * The handler for messages sent from this instance.
      */
     private final Handler mHandler;
@@ -499,18 +193,6 @@
     private final boolean mExtend;
 
     /**
-     * The timer service to use for this AnrTimer.
-     */
-    private final TimerService mTimerService;
-
-    /**
-     * Whether or not canceling a non-existent timer is an error.  Clients often cancel freely
-     * preemptively, without knowing if the timer was ever started.  Keeping this variable true
-     * means that such behavior is not an error.
-     */
-    private final boolean mLenientCancel = true;
-
-    /**
      * The top-level switch for the feature enabled or disabled.
      */
     private final FeatureSwitch mFeature;
@@ -528,40 +210,34 @@
      * AnrTimer may extend the individual timer rather than immediately delivering the timeout to
      * the client.  The extension policy is not part of the instance.
      *
-     * This method accepts an {@link #Injector} to tune behavior for testing.  This method should
-     * not be called directly by regular clients.
-     *
      * @param handler The handler to which the expiration message will be delivered.
      * @param what The "what" parameter for the expiration message.
      * @param label A name for this instance.
      * @param extend A flag to indicate if expired timers can be granted extensions.
-     * @param injector An {@link #Injector} to tune behavior for testing.
+     * @param injector An injector to provide overrides for testing.
      */
     @VisibleForTesting
     AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend,
-            @NonNull Injector injector) {
+             @NonNull Injector injector) {
         mHandler = handler;
         mWhat = what;
         mLabel = label;
         mExtend = extend;
-        boolean enabled = injector.isFeatureEnabled();
-        if (!enabled) {
-            mFeature = new FeatureDisabled();
-            mTimerService = null;
-        } else {
-            mFeature = new FeatureEnabled();
-            mTimerService = new HandlerTimerService(injector);
-
-            synchronized (sAnrTimerList) {
-                sAnrTimerList.add(new WeakReference(this));
-            }
-        }
-        Log.i(TAG, formatSimple("created %s label: \"%s\"", mTimerService, label));
+        mFeature = new FeatureDisabled();
     }
 
     /**
-     * Create an AnrTimer instance with the default {@link #Injector}.  See {@link AnrTimer(Handler,
-     * int, String, boolean, Injector} for a functional description.
+     * Create one AnrTimer instance.  The instance is given a handler and a "what".  Individual
+     * timers are started with {@link #start}.  If a timer expires, then a {@link Message} is sent
+     * immediately to the handler with {@link Message.what} set to what and {@link Message.obj} set
+     * to the timer key.
+     *
+     * AnrTimer instances have a label, which must be unique.  The label is used for reporting and
+     * debug.
+     *
+     * If an individual timer expires internally, and the "extend" parameter is true, then the
+     * AnrTimer may extend the individual timer rather than immediately delivering the timeout to
+     * the client.  The extension policy is not part of the instance.
      *
      * @param handler The handler to which the expiration message will be delivered.
      * @param what The "what" parameter for the expiration message.
@@ -569,7 +245,7 @@
      * @param extend A flag to indicate if expired timers can be granted extensions.
      */
     public AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
-        this(handler, what, label, extend, new Injector(handler));
+        this(handler, what, label, extend, new Injector());
     }
 
     /**
@@ -596,105 +272,17 @@
     }
 
     /**
-     * Start a trace on the timer.  The trace is laid down in the AnrTimerTrack.
-     */
-    private void traceBegin(Timer t, String what) {
-        if (ENABLE_TRACING) {
-            final String label = formatSimple("%s(%d,%d,%s)", what, t.pid, t.uid, mLabel);
-            final int cookie = t.hashCode();
-            Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK, label, cookie);
-        }
-    }
-
-    /**
-     * End a trace on the timer.
-     */
-    private void traceEnd(Timer t) {
-        if (ENABLE_TRACING) {
-            final int cookie = t.hashCode();
-            Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK, cookie);
-        }
-    }
-
-    /**
-     * Return the string representation for a timer status.
-     */
-    private static String statusString(int s) {
-        switch (s) {
-            case TIMER_INVALID: return "invalid";
-            case TIMER_RUNNING: return "running";
-            case TIMER_EXPIRED: return "expired";
-        }
-        return formatSimple("unknown: %d", s);
-    }
-
-    /**
-     * Delete the timer associated with arg from the maps and return it.  Return null if the timer
-     * was not found.
-     */
-    @GuardedBy("mLock")
-    private Timer removeLocked(V arg) {
-        Timer timer = mTimerMap.remove(arg);
-        return timer;
-    }
-
-    /**
-     * Return the number of timers currently running.
-     */
-    @VisibleForTesting
-    static int sizeOfTimerList() {
-        synchronized (sAnrTimerList) {
-            int totalTimers = 0;
-            for (int i = 0; i < sAnrTimerList.size(); i++) {
-                AnrTimer client = sAnrTimerList.get(i).get();
-                if (client != null) totalTimers += client.mTimerMap.size();
-            }
-            return totalTimers;
-        }
-    }
-
-    /**
-     * Clear out all existing timers.  This will lead to unexpected behavior if used carelessly.
-     * It is available only for testing.  It returns the number of times that were actually
-     * erased.
-     */
-    @VisibleForTesting
-    static int resetTimerListForHermeticTest() {
-        synchronized (sAnrTimerList) {
-            int mapLen = 0;
-            for (int i = 0; i < sAnrTimerList.size(); i++) {
-                AnrTimer client = sAnrTimerList.get(i).get();
-                if (client != null) {
-                    mapLen += client.mTimerMap.size();
-                    client.mTimerMap.clear();
-                }
-            }
-            if (mapLen > 0) {
-                Log.w(TAG, formatSimple("erasing timer list: clearing %d timers", mapLen));
-            }
-            return mapLen;
-        }
-    }
-
-    /**
-     * Generate a log message for a timer.
-     */
-    private void report(@NonNull Timer timer, @NonNull String msg) {
-        Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg));
-    }
-
-    /**
      * The FeatureSwitch class provides a quick switch between feature-enabled behavior and
      * feature-disabled behavior.
      */
     private abstract class FeatureSwitch {
-        abstract boolean start(@NonNull V arg, int pid, int uid, long timeoutMs);
+        abstract void start(@NonNull V arg, int pid, int uid, long timeoutMs);
 
-        abstract boolean cancel(@NonNull V arg);
+        abstract void cancel(@NonNull V arg);
 
-        abstract boolean accept(@NonNull V arg);
+        abstract void accept(@NonNull V arg);
 
-        abstract boolean discard(@NonNull V arg);
+        abstract void discard(@NonNull V arg);
 
         abstract boolean enabled();
     }
@@ -706,29 +294,25 @@
     private class FeatureDisabled extends FeatureSwitch {
         /** Start a timer by sending a message to the client's handler. */
         @Override
-        boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+        void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
             final Message msg = mHandler.obtainMessage(mWhat, arg);
             mHandler.sendMessageDelayed(msg, timeoutMs);
-            return true;
         }
 
         /** Cancel a timer by removing the message from the client's handler. */
         @Override
-        boolean cancel(@NonNull V arg) {
+        void cancel(@NonNull V arg) {
             mHandler.removeMessages(mWhat, arg);
-            return true;
         }
 
         /** accept() is a no-op when the feature is disabled. */
         @Override
-        boolean accept(@NonNull V arg) {
-            return true;
+        void accept(@NonNull V arg) {
         }
 
         /** discard() is a no-op when the feature is disabled. */
         @Override
-        boolean discard(@NonNull V arg) {
-            return true;
+        void discard(@NonNull V arg) {
         }
 
         /** The feature is not enabled. */
@@ -739,113 +323,6 @@
     }
 
     /**
-     * The FeatureEnabled class enables the AnrTimer logic.  It is used when the AnrTimer service
-     * is enabled via Flags.anrTimerServiceEnabled.
-     */
-    private class FeatureEnabled extends FeatureSwitch {
-
-        /**
-         * Start a timer.
-         */
-        @Override
-        boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
-            final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, AnrTimer.this);
-            synchronized (mLock) {
-                Timer old = mTimerMap.get(arg);
-                // There is an existing timer.  If the timer was running, then cancel the running
-                // timer and restart it.  If the timer was expired record a protocol error and
-                // discard the expired timer.
-                if (old != null) {
-                    if (old.status == TIMER_EXPIRED) {
-                      restartedLocked(old.status, arg);
-                        discard(arg);
-                    } else {
-                        cancel(arg);
-                    }
-                }
-                if (mTimerService.start(timer)) {
-                    timer.status = TIMER_RUNNING;
-                    mTimerMap.put(arg, timer);
-                    mTotalStarted++;
-                    mMaxStarted = Math.max(mMaxStarted, mTimerMap.size());
-                    if (DEBUG) report(timer, "start");
-                    return true;
-                } else {
-                    Log.e(TAG, "AnrTimer.start failed");
-                    return false;
-                }
-            }
-        }
-
-        /**
-         * Cancel a timer.  Return false if the timer was not found.
-         */
-        @Override
-        boolean cancel(@NonNull V arg) {
-            synchronized (mLock) {
-                Timer timer = removeLocked(arg);
-                if (timer == null) {
-                    if (!mLenientCancel) notFoundLocked("cancel", arg);
-                    return false;
-                }
-                mTimerService.cancel(timer);
-                // There may be an expiration message in flight.  Cancel it.
-                mHandler.removeMessages(mWhat, arg);
-                if (DEBUG) report(timer, "cancel");
-                timer.release();
-                return true;
-            }
-        }
-
-        /**
-         * Accept a timer in the framework-level handler.  The timeout has been accepted and the
-         * timeout handler is executing.  Return false if the timer was not found.
-         */
-        @Override
-        boolean accept(@NonNull V arg) {
-            synchronized (mLock) {
-                Timer timer = removeLocked(arg);
-                if (timer == null) {
-                    notFoundLocked("accept", arg);
-                    return false;
-                }
-                mTimerService.accept(timer);
-                traceEnd(timer);
-                if (DEBUG) report(timer, "accept");
-                timer.release();
-                return true;
-            }
-        }
-
-        /**
-         * Discard a timer in the framework-level handler.  For whatever reason, the timer is no
-         * longer interesting.  No statistics are collected.  Return false if the time was not
-         * found.
-         */
-        @Override
-        boolean discard(@NonNull V arg) {
-            synchronized (mLock) {
-                Timer timer = removeLocked(arg);
-                if (timer == null) {
-                    notFoundLocked("discard", arg);
-                    return false;
-                }
-                mTimerService.discard(timer);
-                traceEnd(timer);
-                if (DEBUG) report(timer, "discard");
-                timer.release();
-                return true;
-            }
-        }
-
-        /** The feature is enabled. */
-        @Override
-        boolean enabled() {
-            return true;
-        }
-    }
-
-    /**
      * Start a timer associated with arg.  The same object must be used to cancel, accept, or
      * discard a timer later.  If a timer already exists with the same arg, then the existing timer
      * is canceled and a new timer is created.
@@ -854,32 +331,27 @@
      * @param pid The Linux process ID of the target being timed.
      * @param uid The Linux user ID of the target being timed.
      * @param timeoutMs The timer timeout, in milliseconds.
-     * @return true if the timer was successfully created.
      */
-    public boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
-        return mFeature.start(arg, pid, uid, timeoutMs);
+    public void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+        mFeature.start(arg, pid, uid, timeoutMs);
     }
 
     /**
      * Cancel the running timer associated with arg.  The timer is forgotten.  If the timer has
      * expired, the call is treated as a discard.  No errors are reported if the timer does not
      * exist or if the timer has expired.
-     *
-     * @return true if the timer was found and was running.
      */
-    public boolean cancel(@NonNull V arg) {
-        return mFeature.cancel(arg);
+    public void cancel(@NonNull V arg) {
+        mFeature.cancel(arg);
     }
 
     /**
      * Accept the expired timer associated with arg.  This indicates that the caller considers the
      * timer expiration to be a true ANR.  (See {@link #discard} for an alternate response.)  It is
      * an error to accept a running timer, however the running timer will be canceled.
-     *
-     * @return true if the timer was found and was expired.
      */
-    public boolean accept(@NonNull V arg) {
-        return mFeature.accept(arg);
+    public void accept(@NonNull V arg) {
+        mFeature.accept(arg);
     }
 
     /**
@@ -889,24 +361,9 @@
      * such a process could be stopped at a breakpoint and its failure to respond would not be an
      * error.  It is an error to discard a running timer, however the running timer will be
      * canceled.
-     *
-     * @return true if the timer was found and was expired.
      */
-    public boolean discard(@NonNull V arg) {
-        return mFeature.discard(arg);
-    }
-
-    /**
-     * The notifier that a timer has fired.  The timer is not modified.
-     */
-    @GuardedBy("mLock")
-    private void onExpiredLocked(@NonNull Timer timer) {
-        if (DEBUG) report(timer, "expire");
-        traceBegin(timer, "expired");
-        mHandler.sendMessage(Message.obtain(mHandler, mWhat, timer.arg));
-        synchronized (mLock) {
-            mTotalExpired++;
-        }
+    public void discard(@NonNull V arg) {
+        mFeature.discard(arg);
     }
 
     /**
@@ -916,9 +373,7 @@
         synchronized (mLock) {
             pw.format("timer: %s\n", mLabel);
             pw.increaseIndent();
-            pw.format("started=%d maxStarted=%d running=%d expired=%d error=%d\n",
-                    mTotalStarted, mMaxStarted, mTimerMap.size(),
-                    mTotalExpired, mTotalErrors);
+            pw.format("started=%d errors=%d\n", mTotalStarted, mTotalErrors);
             pw.decreaseIndent();
         }
     }
@@ -931,10 +386,20 @@
     }
 
     /**
-     * The current time in milliseconds.
+     * Dump all errors to the output stream.
      */
-    private static long now() {
-        return SystemClock.uptimeMillis();
+    private static void dumpErrors(IndentingPrintWriter ipw) {
+        Error errors[];
+        synchronized (sErrors) {
+            if (sErrors.size() == 0) return;
+            errors = sErrors.toArray();
+        }
+        ipw.println("Errors");
+        ipw.increaseIndent();
+        for (int i = 0; i < errors.length; i++) {
+            if (errors[i] != null) errors[i].dump(ipw, i);
+        }
+        ipw.decreaseIndent();
     }
 
     /**
@@ -966,60 +431,12 @@
     }
 
     /**
-     * Log an error about a timer that is started when there is an existing timer.
-     */
-    @GuardedBy("mLock")
-    private void restartedLocked(@TimerStatus int status, Object arg) {
-        recordErrorLocked("start", status == TIMER_EXPIRED ? "autoDiscard" : "autoCancel", arg);
-    }
-
-    /**
-     * Dump a single error to the output stream.
-     */
-    private static void dump(IndentingPrintWriter ipw, int seq, Error err) {
-        ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag,
-                err.issue, err.arg);
-
-        final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime();
-        final long etime = offset + err.timestamp;
-        ipw.println("    date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime));
-        ipw.increaseIndent();
-        for (int i = 0; i < err.stack.length; i++) {
-            ipw.println("    " + err.stack[i].toString());
-        }
-        ipw.decreaseIndent();
-    }
-
-    /**
-     * Dump all errors to the output stream.
-     */
-    private static void dumpErrors(IndentingPrintWriter ipw) {
-        Error errors[];
-        synchronized (sErrors) {
-            if (sErrors.size() == 0) return;
-            errors = sErrors.toArray();
-        }
-        ipw.println("Errors");
-        ipw.increaseIndent();
-        for (int i = 0; i < errors.length; i++) {
-            if (errors[i] != null) dump(ipw, i, errors[i]);
-        }
-        ipw.decreaseIndent();
-    }
-
-    /**
      * Dumpsys output.
      */
     public static void dump(@NonNull PrintWriter pw, boolean verbose) {
         final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
         ipw.println("AnrTimer statistics");
         ipw.increaseIndent();
-        synchronized (sAnrTimerList) {
-            for (int i = 0; i < sAnrTimerList.size(); i++) {
-                AnrTimer client = sAnrTimerList.get(i).get();
-                if (client != null) client.dump(ipw);
-            }
-        }
         if (verbose) dumpErrors(ipw);
         ipw.format("AnrTimerEnd\n");
         ipw.decreaseIndent();
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
new file mode 100644
index 0000000..975fdc0
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -0,0 +1,345 @@
+/*
+ * 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.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS;
+import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
+import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS;
+import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.view.DisplayInfo;
+import android.window.DisplayAreaInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.wm.utils.DisplayInfoOverrides.DisplayInfoFieldsUpdater;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A DisplayUpdater that could defer and queue display updates coming from DisplayManager to
+ * WindowManager. It allows to defer pending display updates if WindowManager is currently not
+ * ready to apply them.
+ * For example, this might happen if there is a Shell transition running and physical display
+ * changed. We can't immediately apply the display updates because we want to start a separate
+ * display change transition. In this case, we will queue all display updates until the current
+ * transition's collection finishes and then apply them afterwards.
+ */
+public class DeferredDisplayUpdater implements DisplayUpdater {
+
+    /**
+     * List of fields that could be deferred before applying to DisplayContent.
+     * This should be kept in sync with {@link DeferredDisplayUpdater#calculateDisplayInfoDiff}
+     */
+    @VisibleForTesting
+    static final DisplayInfoFieldsUpdater DEFERRABLE_FIELDS = (out, override) -> {
+        // Treat unique id and address change as WM-specific display change as we re-query display
+        // settings and parameters based on it which could cause window changes
+        out.uniqueId = override.uniqueId;
+        out.address = override.address;
+
+        // Also apply WM-override fields, since they might produce differences in window hierarchy
+        WM_OVERRIDE_FIELDS.setFields(out, override);
+    };
+
+    private final DisplayContent mDisplayContent;
+
+    @NonNull
+    private final DisplayInfo mNonOverrideDisplayInfo = new DisplayInfo();
+
+    /**
+     * The last known display parameters from DisplayManager, some WM-specific fields in this object
+     * might not be applied to the DisplayContent yet
+     */
+    @Nullable
+    private DisplayInfo mLastDisplayInfo;
+
+    /**
+     * The last DisplayInfo that was applied to DisplayContent, only WM-specific parameters must be
+     * used from this object. This object is used to store old values of DisplayInfo while these
+     * fields are pending to be applied to DisplayContent.
+     */
+    @Nullable
+    private DisplayInfo mLastWmDisplayInfo;
+
+    @NonNull
+    private final DisplayInfo mOutputDisplayInfo = new DisplayInfo();
+
+    public DeferredDisplayUpdater(@NonNull DisplayContent displayContent) {
+        mDisplayContent = displayContent;
+        mNonOverrideDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
+    }
+
+    /**
+     * Reads the latest display parameters from the display manager and returns them in a callback.
+     * If there are pending display updates, it will wait for them to finish first and only then it
+     * will call the callback with the latest display parameters.
+     *
+     * @param finishCallback is called when all pending display updates are finished
+     */
+    @Override
+    public void updateDisplayInfo(@NonNull Runnable finishCallback) {
+        // Get the latest display parameters from the DisplayManager
+        final DisplayInfo displayInfo = getCurrentDisplayInfo();
+
+        final int displayInfoDiff = calculateDisplayInfoDiff(mLastDisplayInfo, displayInfo);
+        final boolean physicalDisplayUpdated = isPhysicalDisplayUpdated(mLastDisplayInfo,
+                displayInfo);
+
+        mLastDisplayInfo = displayInfo;
+
+        // Apply whole display info immediately as is if either:
+        // * it is the first display update
+        // * shell transitions are disabled or temporary unavailable
+        if (displayInfoDiff == DIFF_EVERYTHING
+                || !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+                    "DeferredDisplayUpdater: applying DisplayInfo immediately");
+
+            mLastWmDisplayInfo = displayInfo;
+            applyLatestDisplayInfo();
+            finishCallback.run();
+            return;
+        }
+
+        // If there are non WM-specific display info changes, apply only these fields immediately
+        if ((displayInfoDiff & DIFF_NOT_WM_DEFERRABLE) > 0) {
+            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+                    "DeferredDisplayUpdater: partially applying DisplayInfo immediately");
+            applyLatestDisplayInfo();
+        }
+
+        // If there are WM-specific display info changes, apply them through a Shell transition
+        if ((displayInfoDiff & DIFF_WM_DEFERRABLE) > 0) {
+            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+                    "DeferredDisplayUpdater: deferring DisplayInfo update");
+
+            requestDisplayChangeTransition(physicalDisplayUpdated, () -> {
+                // Apply deferrable fields to DisplayContent only when the transition
+                // starts collecting, non-deferrable fields are ignored in mLastWmDisplayInfo
+                mLastWmDisplayInfo = displayInfo;
+                applyLatestDisplayInfo();
+                finishCallback.run();
+            });
+        } else {
+            // There are no WM-specific updates, so we can immediately notify that all display
+            // info changes are applied
+            finishCallback.run();
+        }
+    }
+
+    /**
+     * Requests a display change Shell transition
+     *
+     * @param physicalDisplayUpdated if true also starts remote display change
+     * @param onStartCollect         called when the Shell transition starts collecting
+     */
+    private void requestDisplayChangeTransition(boolean physicalDisplayUpdated,
+            @NonNull Runnable onStartCollect) {
+
+        final Transition transition = new Transition(TRANSIT_CHANGE, /* flags= */ 0,
+                mDisplayContent.mTransitionController,
+                mDisplayContent.mTransitionController.mSyncEngine);
+
+        mDisplayContent.mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+
+        mDisplayContent.mTransitionController.startCollectOrQueue(transition, deferred -> {
+            final Rect startBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth,
+                    mDisplayContent.mInitialDisplayHeight);
+            final int fromRotation = mDisplayContent.getRotation();
+
+            onStartCollect.run();
+
+            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+                    "DeferredDisplayUpdater: applied DisplayInfo after deferring");
+
+            if (physicalDisplayUpdated) {
+                onDisplayUpdated(transition, fromRotation, startBounds);
+            } else {
+                transition.setAllReady();
+            }
+        });
+    }
+
+    /**
+     * Applies current DisplayInfo to DisplayContent, DisplayContent is merged from two parts:
+     * - non-deferrable fields are set from the most recent values received from DisplayManager
+     * (uses {@link mLastDisplayInfo} field)
+     * - deferrable fields are set from the latest values that we could apply to WM
+     * (uses {@link mLastWmDisplayInfo} field)
+     */
+    private void applyLatestDisplayInfo() {
+        copyDisplayInfoFields(mOutputDisplayInfo, /* base= */ mLastDisplayInfo,
+                /* override= */ mLastWmDisplayInfo, /* fields= */ DEFERRABLE_FIELDS);
+        mDisplayContent.onDisplayInfoUpdated(mOutputDisplayInfo);
+    }
+
+    @NonNull
+    private DisplayInfo getCurrentDisplayInfo() {
+        mDisplayContent.mWmService.mDisplayManagerInternal.getNonOverrideDisplayInfo(
+                mDisplayContent.mDisplayId, mNonOverrideDisplayInfo);
+        return new DisplayInfo(mNonOverrideDisplayInfo);
+    }
+
+    /**
+     * Called when physical display is updated, this could happen e.g. on foldable
+     * devices when the physical underlying display is replaced. This method should be called
+     * when the new display info is already applied to the WM hierarchy.
+     *
+     * @param fromRotation rotation before the display change
+     * @param startBounds  display bounds before the display change
+     */
+    private void onDisplayUpdated(@NonNull Transition transition, int fromRotation,
+            @NonNull Rect startBounds) {
+        final Rect endBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth,
+                mDisplayContent.mInitialDisplayHeight);
+        final int toRotation = mDisplayContent.getRotation();
+
+        final TransitionRequestInfo.DisplayChange displayChange =
+                new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId());
+        displayChange.setStartAbsBounds(startBounds);
+        displayChange.setEndAbsBounds(endBounds);
+        displayChange.setStartRotation(fromRotation);
+        displayChange.setEndRotation(toRotation);
+        displayChange.setPhysicalDisplayChanged(true);
+
+        mDisplayContent.mTransitionController.requestStartTransition(transition,
+                /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+
+        final DisplayAreaInfo newDisplayAreaInfo = mDisplayContent.getDisplayAreaInfo();
+
+        final boolean startedRemoteChange = mDisplayContent.mRemoteDisplayChangeController
+                .performRemoteDisplayChange(fromRotation, toRotation, newDisplayAreaInfo,
+                        transaction -> finishDisplayUpdate(transaction, transition));
+
+        if (!startedRemoteChange) {
+            finishDisplayUpdate(/* wct= */ null, transition);
+        }
+    }
+
+    private void finishDisplayUpdate(@Nullable WindowContainerTransaction wct,
+            @NonNull Transition transition) {
+        if (wct != null) {
+            mDisplayContent.mAtmService.mWindowOrganizerController.applyTransaction(
+                    wct);
+        }
+        transition.setAllReady();
+    }
+
+    private boolean isPhysicalDisplayUpdated(@Nullable DisplayInfo first,
+            @Nullable DisplayInfo second) {
+        if (first == null || second == null) return true;
+        return !Objects.equals(first.uniqueId, second.uniqueId);
+    }
+
+    /**
+     * Diff result: fields are the same
+     */
+    static final int DIFF_NONE = 0;
+
+    /**
+     * Diff result: fields that could be deferred in WM are different
+     */
+    static final int DIFF_WM_DEFERRABLE = 1 << 0;
+
+    /**
+     * Diff result: fields that could not be deferred in WM are different
+     */
+    static final int DIFF_NOT_WM_DEFERRABLE = 1 << 1;
+
+    /**
+     * Diff result: everything is different
+     */
+    static final int DIFF_EVERYTHING = 0XFFFFFFFF;
+
+    @VisibleForTesting
+    static int calculateDisplayInfoDiff(@Nullable DisplayInfo first, @Nullable DisplayInfo second) {
+        int diff = DIFF_NONE;
+
+        if (Objects.equals(first, second)) return diff;
+        if (first == null || second == null) return DIFF_EVERYTHING;
+
+        if (first.layerStack != second.layerStack
+                || first.flags != second.flags
+                || first.type != second.type
+                || first.displayId != second.displayId
+                || first.displayGroupId != second.displayGroupId
+                || !Objects.equals(first.deviceProductInfo, second.deviceProductInfo)
+                || first.modeId != second.modeId
+                || first.renderFrameRate != second.renderFrameRate
+                || first.defaultModeId != second.defaultModeId
+                || first.userPreferredModeId != second.userPreferredModeId
+                || !Arrays.equals(first.supportedModes, second.supportedModes)
+                || first.colorMode != second.colorMode
+                || !Arrays.equals(first.supportedColorModes, second.supportedColorModes)
+                || !Objects.equals(first.hdrCapabilities, second.hdrCapabilities)
+                || !Arrays.equals(first.userDisabledHdrTypes, second.userDisabledHdrTypes)
+                || first.minimalPostProcessingSupported != second.minimalPostProcessingSupported
+                || first.appVsyncOffsetNanos != second.appVsyncOffsetNanos
+                || first.presentationDeadlineNanos != second.presentationDeadlineNanos
+                || first.state != second.state
+                || first.committedState != second.committedState
+                || first.ownerUid != second.ownerUid
+                || !Objects.equals(first.ownerPackageName, second.ownerPackageName)
+                || first.removeMode != second.removeMode
+                || first.getRefreshRate() != second.getRefreshRate()
+                || first.brightnessMinimum != second.brightnessMinimum
+                || first.brightnessMaximum != second.brightnessMaximum
+                || first.brightnessDefault != second.brightnessDefault
+                || first.installOrientation != second.installOrientation
+                || !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate)
+                || !BrightnessSynchronizer.floatEquals(first.hdrSdrRatio, second.hdrSdrRatio)
+                || !first.thermalRefreshRateThrottling.contentEquals(
+                second.thermalRefreshRateThrottling)
+                || !Objects.equals(first.thermalBrightnessThrottlingDataId,
+                second.thermalBrightnessThrottlingDataId)) {
+            diff |= DIFF_NOT_WM_DEFERRABLE;
+        }
+
+        if (first.appWidth != second.appWidth
+                || first.appHeight != second.appHeight
+                || first.smallestNominalAppWidth != second.smallestNominalAppWidth
+                || first.smallestNominalAppHeight != second.smallestNominalAppHeight
+                || first.largestNominalAppWidth != second.largestNominalAppWidth
+                || first.largestNominalAppHeight != second.largestNominalAppHeight
+                || first.logicalWidth != second.logicalWidth
+                || first.logicalHeight != second.logicalHeight
+                || first.physicalXDpi != second.physicalXDpi
+                || first.physicalYDpi != second.physicalYDpi
+                || first.rotation != second.rotation
+                || !Objects.equals(first.displayCutout, second.displayCutout)
+                || first.logicalDensityDpi != second.logicalDensityDpi
+                || !Objects.equals(first.roundedCorners, second.roundedCorners)
+                || !Objects.equals(first.displayShape, second.displayShape)
+                || !Objects.equals(first.uniqueId, second.uniqueId)
+                || !Objects.equals(first.address, second.address)
+        ) {
+            diff |= DIFF_WM_DEFERRABLE;
+        }
+
+        return diff;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a840973..3f20624 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -276,6 +276,7 @@
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import static com.android.window.flags.Flags.deferDisplayUpdates;
 
 /**
  * Utility class for keeping track of the WindowStates and other pertinent contents of a
@@ -545,10 +546,12 @@
     boolean isDefaultDisplay;
 
     /** Detect user tapping outside of current focused task bounds .*/
+    // TODO(b/315321016): Remove once pointer event detection is removed from WM.
     @VisibleForTesting
     final TaskTapPointerEventListener mTapDetector;
 
     /** Detect user tapping outside of current focused root task bounds .*/
+    // TODO(b/315321016): Remove once pointer event detection is removed from WM.
     private Region mTouchExcludeRegion = new Region();
 
     /** Save allocating when calculating rects */
@@ -1158,7 +1161,11 @@
         mWallpaperController.resetLargestDisplay(display);
         display.getDisplayInfo(mDisplayInfo);
         display.getMetrics(mDisplayMetrics);
-        mDisplayUpdater = new ImmediateDisplayUpdater(this);
+        if (deferDisplayUpdates()) {
+            mDisplayUpdater = new DeferredDisplayUpdater(this);
+        } else {
+            mDisplayUpdater = new ImmediateDisplayUpdater(this);
+        }
         mSystemGestureExclusionLimit = mWmService.mConstants.mSystemGestureExclusionLimitDp
                 * mDisplayMetrics.densityDpi / DENSITY_DEFAULT;
         isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
@@ -1189,12 +1196,18 @@
                 "PointerEventDispatcher" + mDisplayId, mDisplayId);
         mPointerEventDispatcher = new PointerEventDispatcher(inputChannel);
 
-        // Tap Listeners are supported for:
-        // 1. All physical displays (multi-display).
-        // 2. VirtualDisplays on VR, AA (and everything else).
-        mTapDetector = new TaskTapPointerEventListener(mWmService, this);
-        registerPointerEventListener(mTapDetector);
-        registerPointerEventListener(mWmService.mMousePositionTracker);
+        if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
+            mTapDetector = null;
+        } else {
+            // Tap Listeners are supported for:
+            // 1. All physical displays (multi-display).
+            // 2. VirtualDisplays on VR, AA (and everything else).
+            mTapDetector = new TaskTapPointerEventListener(mWmService, this);
+            registerPointerEventListener(mTapDetector);
+        }
+        if (mWmService.mMousePositionTracker != null) {
+            registerPointerEventListener(mWmService.mMousePositionTracker);
+        }
         if (mWmService.mAtmService.getRecentTasks() != null) {
             registerPointerEventListener(
                     mWmService.mAtmService.getRecentTasks().getInputListener());
@@ -1636,12 +1649,13 @@
         mWmService.mWindowPlacerLocked.performSurfacePlacement();
     }
 
-    void sendNewConfiguration() {
+    /** Returns {@code true} if the display configuration is changed. */
+    boolean sendNewConfiguration() {
         if (!isReady()) {
-            return;
+            return false;
         }
         if (mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange()) {
-            return;
+            return false;
         }
 
         final Transition.ReadyCondition displayConfig = mTransitionController.isCollecting()
@@ -1656,7 +1670,7 @@
             displayConfig.meet();
         }
         if (configUpdated) {
-            return;
+            return true;
         }
 
         // The display configuration doesn't change. If there is a launching transformed app, that
@@ -1674,6 +1688,7 @@
             setLayoutNeeded();
             mWmService.mWindowPlacerLocked.performSurfacePlacement();
         }
+        return false;
     }
 
     @Override
@@ -3260,6 +3275,12 @@
     }
 
     void updateTouchExcludeRegion() {
+        if (mTapDetector == null) {
+            // The touch exclude region is used to detect the region outside of the focused task
+            // so that the tap detector can detect outside touches. Don't calculate the exclude
+            // region when the tap detector is disabled.
+            return;
+        }
         final Task focusedTask = (mFocusedApp != null ? mFocusedApp.getTask() : null);
         if (focusedTask == null) {
             mTouchExcludeRegion.setEmpty();
@@ -3298,6 +3319,11 @@
     }
 
     private void processTaskForTouchExcludeRegion(Task task, Task focusedTask, int delta) {
+        if (mTapDetector == null) {
+            // The touch exclude region is used to detect the region outside of the focused task
+            // so that the tap detector can detect outside touches. Don't calculate the exclude
+            // region when the tap detector is disabled.
+        }
         final ActivityRecord topVisibleActivity = task.getTopVisibleActivity();
 
         if (topVisibleActivity == null || !topVisibleActivity.hasContentToDisplay()) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index b862d7c..460a68f 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -39,6 +39,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
@@ -272,6 +273,8 @@
 
     private @InsetsType int mForciblyShownTypes;
 
+    private boolean mImeInsetsConsumed;
+
     private boolean mIsImmersiveMode;
 
     // The windows we were told about in focusChanged.
@@ -1420,6 +1423,7 @@
         mShowingDream = false;
         mIsFreeformWindowOverlappingWithNavBar = false;
         mForciblyShownTypes = 0;
+        mImeInsetsConsumed = false;
     }
 
     /**
@@ -1481,6 +1485,17 @@
             mForciblyShownTypes |= win.mAttrs.forciblyShownTypes;
         }
 
+        if (win.mImeInsetsConsumed != mImeInsetsConsumed) {
+            win.mImeInsetsConsumed = mImeInsetsConsumed;
+            final WindowState imeWin = mDisplayContent.mInputMethodWindow;
+            if (win.isReadyToDispatchInsetsState() && imeWin != null && imeWin.isVisible()) {
+                win.notifyInsetsChanged();
+            }
+        }
+        if ((attrs.privateFlags & PRIVATE_FLAG_CONSUME_IME_INSETS) != 0 && win.isVisible()) {
+            mImeInsetsConsumed = true;
+        }
+
         if (!affectsSystemUi) {
             return;
         }
@@ -2828,6 +2843,7 @@
             }
         }
         pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen);
+        pw.print(prefix); pw.print("mImeInsetsConsumed="); pw.println(mImeInsetsConsumed);
         pw.print(prefix); pw.print("mForceShowNavigationBarEnabled=");
         pw.print(mForceShowNavigationBarEnabled);
         pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index c089d10..7815679 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -391,13 +391,26 @@
 
             if (originalImeSource != null) {
                 final boolean imeVisibility = w.isRequestedVisible(Type.ime());
-                final InsetsState state = copyState ? new InsetsState(originalState)
+                final InsetsState state = copyState
+                        ? new InsetsState(originalState)
                         : originalState;
                 final InsetsSource imeSource = new InsetsSource(originalImeSource);
                 imeSource.setVisible(imeVisibility);
                 state.addSource(imeSource);
                 return state;
             }
+        } else if (w.mImeInsetsConsumed) {
+            // Set the IME source (if there is one) to be invisible if it has been consumed.
+            final InsetsSource originalImeSource = originalState.peekSource(ID_IME);
+            if (originalImeSource != null && originalImeSource.isVisible()) {
+                final InsetsState state = copyState
+                        ? new InsetsState(originalState)
+                        : originalState;
+                final InsetsSource imeSource = new InsetsSource(originalImeSource);
+                imeSource.setVisible(false);
+                state.addSource(imeSource);
+                return state;
+            }
         }
         return originalState;
     }
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index 23c135a..03574029 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -26,6 +26,7 @@
 import android.view.Display.Mode;
 import android.view.DisplayInfo;
 import android.view.Surface;
+import android.view.SurfaceControl;
 import android.view.SurfaceControl.RefreshRateRange;
 
 import java.util.HashMap;
@@ -191,26 +192,35 @@
     public static class FrameRateVote {
         float mRefreshRate;
         @Surface.FrameRateCompatibility int mCompatibility;
+        @SurfaceControl.FrameRateSelectionStrategy int mSelectionStrategy;
 
-        FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
-            update(refreshRate, compatibility);
+
+
+        FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility,
+                      @SurfaceControl.FrameRateSelectionStrategy int selectionStrategy) {
+            update(refreshRate, compatibility, selectionStrategy);
         }
 
         FrameRateVote() {
             reset();
         }
 
-        boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
-            if (!refreshRateEquals(refreshRate) || mCompatibility != compatibility) {
+        boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility,
+                       @SurfaceControl.FrameRateSelectionStrategy int selectionStrategy) {
+            if (!refreshRateEquals(refreshRate)
+                    || mCompatibility != compatibility
+                    || mSelectionStrategy != selectionStrategy) {
                 mRefreshRate = refreshRate;
                 mCompatibility = compatibility;
+                mSelectionStrategy = selectionStrategy;
                 return true;
             }
             return false;
         }
 
         boolean reset() {
-            return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+            return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE);
         }
 
         @Override
@@ -221,17 +231,20 @@
 
             FrameRateVote other = (FrameRateVote) o;
             return refreshRateEquals(other.mRefreshRate)
-                    && mCompatibility == other.mCompatibility;
+                    && mCompatibility == other.mCompatibility
+                    && mSelectionStrategy == other.mSelectionStrategy;
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(mRefreshRate, mCompatibility);
+            return Objects.hash(mRefreshRate, mCompatibility, mSelectionStrategy);
+
         }
 
         @Override
         public String toString() {
-            return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility;
+            return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility
+                    + ", mSelectionStrategy=" + mSelectionStrategy;
         }
 
         private boolean refreshRateEquals(float refreshRate) {
@@ -265,7 +278,8 @@
                 for (Display.Mode mode : mDisplayInfo.supportedModes) {
                     if (preferredModeId == mode.getModeId()) {
                         return w.mFrameRateVote.update(mode.getRefreshRate(),
-                                Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+                                Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
                     }
                 }
             }
@@ -273,7 +287,8 @@
 
         if (w.mAttrs.preferredRefreshRate > 0) {
             return w.mFrameRateVote.update(w.mAttrs.preferredRefreshRate,
-                    Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+                    Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
         }
 
         // If the app didn't set a preferred mode id or refresh rate, but it is part of the deny
@@ -282,7 +297,8 @@
             final String packageName = w.getOwningPackage();
             if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
                 return w.mFrameRateVote.update(mLowRefreshRateMode.getRefreshRate(),
-                        Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+                        Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                        SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index 7d22b74..ac244c7 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -45,6 +45,10 @@
 
     public TaskTapPointerEventListener(WindowManagerService service,
             DisplayContent displayContent) {
+        // TODO(b/315321016): Remove this class when the flag rollout is complete.
+        if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
+            throw new IllegalStateException("TaskTapPointerEventListener should not be used!");
+        }
         mService = service;
         mDisplayContent = displayContent;
     }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 76c4a0e..f020bfa 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2868,8 +2868,7 @@
             final WindowContainer<?> wc = mParticipants.valueAt(i);
             final DisplayContent dc = wc.asDisplayContent();
             if (dc == null || !mChanges.get(dc).hasChanged()) continue;
-            final int originalSeq = dc.getConfiguration().seq;
-            dc.sendNewConfiguration();
+            final boolean changed = dc.sendNewConfiguration();
             // Set to ready if no other change controls the ready state. But if there is, such as
             // if an activity is pausing, it will call setReady(ar, false) and wait for the next
             // resumed activity. Then do not set to ready because the transition only contains
@@ -2877,7 +2876,7 @@
             if (!mReadyTrackerOld.mUsed) {
                 setReady(dc, true);
             }
-            if (originalSeq == dc.getConfiguration().seq) continue;
+            if (!changed) continue;
             // If the update is deferred, sendNewConfiguration won't deliver new configuration to
             // clients, then it is the caller's responsibility to deliver the changes.
             if (mController.mAtm.mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
new file mode 100644
index 0000000..1688a1a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -0,0 +1,448 @@
+/*
+ * Copyright 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.graphics.Matrix.MSCALE_X;
+import static android.graphics.Matrix.MSCALE_Y;
+import static android.graphics.Matrix.MSKEW_X;
+import static android.graphics.Matrix.MSKEW_Y;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TPL;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IntArray;
+import android.util.Pair;
+import android.util.Size;
+import android.view.InputWindowHandle;
+import android.window.ITrustedPresentationListener;
+import android.window.TrustedPresentationThresholds;
+import android.window.WindowInfosListener;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.wm.utils.RegionUtils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Optional;
+
+/**
+ * Class to handle TrustedPresentationListener registrations in a thread safe manner. This class
+ * also takes care of cleaning up listeners when the remote process dies.
+ */
+public class TrustedPresentationListenerController {
+
+    // Should only be accessed by the posting to the handler
+    private class Listeners {
+        private final class ListenerDeathRecipient implements IBinder.DeathRecipient {
+            IBinder mListenerBinder;
+            int mInstances;
+
+            ListenerDeathRecipient(IBinder listenerBinder) {
+                mListenerBinder = listenerBinder;
+                mInstances = 0;
+                try {
+                    mListenerBinder.linkToDeath(this, 0);
+                } catch (RemoteException ignore) {
+                }
+            }
+
+            void addInstance() {
+                mInstances++;
+            }
+
+            // return true if there are no instances alive
+            boolean removeInstance() {
+                mInstances--;
+                if (mInstances > 0) {
+                    return false;
+                }
+                mListenerBinder.unlinkToDeath(this, 0);
+                return true;
+            }
+
+            public void binderDied() {
+                mHandler.post(() -> {
+                    mUniqueListeners.remove(mListenerBinder);
+                    removeListeners(mListenerBinder, Optional.empty());
+                });
+            }
+        }
+
+        // tracks binder deaths for cleanup
+        ArrayMap<IBinder, ListenerDeathRecipient> mUniqueListeners = new ArrayMap<>();
+        ArrayMap<IBinder /*window*/, ArrayList<TrustedPresentationInfo>> mWindowToListeners =
+                new ArrayMap<>();
+
+        void register(IBinder window, ITrustedPresentationListener listener,
+                TrustedPresentationThresholds thresholds, int id) {
+            var listenersForWindow = mWindowToListeners.computeIfAbsent(window,
+                    iBinder -> new ArrayList<>());
+            listenersForWindow.add(new TrustedPresentationInfo(thresholds, id, listener));
+
+            // register death listener
+            var listenerBinder = listener.asBinder();
+            var deathRecipient = mUniqueListeners.computeIfAbsent(listenerBinder,
+                    ListenerDeathRecipient::new);
+            deathRecipient.addInstance();
+        }
+
+        void unregister(ITrustedPresentationListener trustedPresentationListener, int id) {
+            var listenerBinder = trustedPresentationListener.asBinder();
+            var deathRecipient = mUniqueListeners.get(listenerBinder);
+            if (deathRecipient == null) {
+                ProtoLog.e(WM_DEBUG_TPL, "unregister failed, couldn't find"
+                        + " deathRecipient for %s with id=%d", trustedPresentationListener, id);
+                return;
+            }
+
+            if (deathRecipient.removeInstance()) {
+                mUniqueListeners.remove(listenerBinder);
+            }
+            removeListeners(listenerBinder, Optional.of(id));
+        }
+
+        boolean isEmpty() {
+            return mWindowToListeners.isEmpty();
+        }
+
+        ArrayList<TrustedPresentationInfo> get(IBinder windowToken) {
+            return mWindowToListeners.get(windowToken);
+        }
+
+        private void removeListeners(IBinder listenerBinder, Optional<Integer> id) {
+            for (int i = mWindowToListeners.size() - 1; i >= 0; i--) {
+                var listeners = mWindowToListeners.valueAt(i);
+                for (int j = listeners.size() - 1; j >= 0; j--) {
+                    var listener = listeners.get(j);
+                    if (listener.mListener.asBinder() == listenerBinder && (id.isEmpty()
+                            || listener.mId == id.get())) {
+                        listeners.remove(j);
+                    }
+                }
+                if (listeners.isEmpty()) {
+                    mWindowToListeners.removeAt(i);
+                }
+            }
+        }
+    }
+
+    private final Object mHandlerThreadLock = new Object();
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+
+    private WindowInfosListener mWindowInfosListener;
+
+    Listeners mRegisteredListeners = new Listeners();
+
+    private InputWindowHandle[] mLastWindowHandles;
+
+    private final Object mIgnoredWindowTokensLock = new Object();
+
+    private final ArraySet<IBinder> mIgnoredWindowTokens = new ArraySet<>();
+
+    private void startHandlerThreadIfNeeded() {
+        synchronized (mHandlerThreadLock) {
+            if (mHandler == null) {
+                mHandlerThread = new HandlerThread("WindowInfosListenerForTpl");
+                mHandlerThread.start();
+                mHandler = new Handler(mHandlerThread.getLooper());
+            }
+        }
+    }
+
+    void addIgnoredWindowTokens(IBinder token) {
+        synchronized (mIgnoredWindowTokensLock) {
+            mIgnoredWindowTokens.add(token);
+        }
+    }
+
+    void removeIgnoredWindowTokens(IBinder token) {
+        synchronized (mIgnoredWindowTokensLock) {
+            mIgnoredWindowTokens.remove(token);
+        }
+    }
+
+    void registerListener(IBinder window, ITrustedPresentationListener listener,
+            TrustedPresentationThresholds thresholds, int id) {
+        startHandlerThreadIfNeeded();
+        mHandler.post(() -> {
+            ProtoLog.d(WM_DEBUG_TPL, "Registering listener=%s with id=%d for window=%s with %s",
+                    listener, id, window, thresholds);
+
+            mRegisteredListeners.register(window, listener, thresholds, id);
+            registerWindowInfosListener();
+            // Update the initial state for the new registered listener
+            computeTpl(mLastWindowHandles);
+        });
+    }
+
+    void unregisterListener(ITrustedPresentationListener listener, int id) {
+        startHandlerThreadIfNeeded();
+        mHandler.post(() -> {
+            ProtoLog.d(WM_DEBUG_TPL, "Unregistering listener=%s with id=%d",
+                    listener, id);
+
+            mRegisteredListeners.unregister(listener, id);
+            if (mRegisteredListeners.isEmpty()) {
+                unregisterWindowInfosListener();
+            }
+        });
+    }
+
+    void dump(PrintWriter pw) {
+        final String innerPrefix = "  ";
+        pw.println("TrustedPresentationListenerController:");
+        pw.println(innerPrefix + "Active unique listeners ("
+                + mRegisteredListeners.mUniqueListeners.size() + "):");
+        for (int i = 0; i < mRegisteredListeners.mWindowToListeners.size(); i++) {
+            pw.println(
+                    innerPrefix + "  window=" + mRegisteredListeners.mWindowToListeners.keyAt(i));
+            final var listeners = mRegisteredListeners.mWindowToListeners.valueAt(i);
+            for (int j = 0; j < listeners.size(); j++) {
+                final var listener = listeners.get(j);
+                pw.println(innerPrefix + innerPrefix + "  listener=" + listener.mListener.asBinder()
+                        + " id=" + listener.mId
+                        + " thresholds=" + listener.mThresholds);
+            }
+        }
+    }
+
+    private void registerWindowInfosListener() {
+        if (mWindowInfosListener != null) {
+            return;
+        }
+
+        mWindowInfosListener = new WindowInfosListener() {
+            @Override
+            public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
+                    DisplayInfo[] displayInfos) {
+                mHandler.post(() -> computeTpl(windowHandles));
+            }
+        };
+        mLastWindowHandles = mWindowInfosListener.register().first;
+    }
+
+    private void unregisterWindowInfosListener() {
+        if (mWindowInfosListener == null) {
+            return;
+        }
+
+        mWindowInfosListener.unregister();
+        mWindowInfosListener = null;
+        mLastWindowHandles = null;
+    }
+
+    private void computeTpl(InputWindowHandle[] windowHandles) {
+        mLastWindowHandles = windowHandles;
+        if (mLastWindowHandles == null || mLastWindowHandles.length == 0
+                || mRegisteredListeners.isEmpty()) {
+            return;
+        }
+
+        Rect tmpRect = new Rect();
+        Matrix tmpInverseMatrix = new Matrix();
+        float[] tmpMatrix = new float[9];
+        Region coveredRegionsAbove = new Region();
+        long currTimeMs = System.currentTimeMillis();
+        ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.length);
+
+        ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates =
+                new ArrayMap<>();
+        ArraySet<IBinder> ignoredWindowTokens;
+        synchronized (mIgnoredWindowTokensLock) {
+            ignoredWindowTokens = new ArraySet<>(mIgnoredWindowTokens);
+        }
+        for (var windowHandle : mLastWindowHandles) {
+            if (ignoredWindowTokens.contains(windowHandle.getWindowToken())) {
+                ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
+                continue;
+            }
+            tmpRect.set(windowHandle.frame);
+            var listeners = mRegisteredListeners.get(windowHandle.getWindowToken());
+            if (listeners != null) {
+                Region region = new Region();
+                region.op(tmpRect, coveredRegionsAbove, Region.Op.DIFFERENCE);
+                windowHandle.transform.invert(tmpInverseMatrix);
+                tmpInverseMatrix.getValues(tmpMatrix);
+                float scaleX = (float) Math.sqrt(tmpMatrix[MSCALE_X] * tmpMatrix[MSCALE_X]
+                        + tmpMatrix[MSKEW_X] * tmpMatrix[MSKEW_X]);
+                float scaleY = (float) Math.sqrt(tmpMatrix[MSCALE_Y] * tmpMatrix[MSCALE_Y]
+                        + tmpMatrix[MSKEW_Y] * tmpMatrix[MSKEW_Y]);
+
+                float fractionRendered = computeFractionRendered(region, new RectF(tmpRect),
+                        windowHandle.contentSize,
+                        scaleX, scaleY);
+
+                checkIfInThreshold(listeners, listenerUpdates, fractionRendered, windowHandle.alpha,
+                        currTimeMs);
+            }
+
+            coveredRegionsAbove.op(tmpRect, Region.Op.UNION);
+            ProtoLog.v(WM_DEBUG_TPL, "coveredRegionsAbove updated with %s frame:%s region:%s",
+                    windowHandle.name, tmpRect.toShortString(), coveredRegionsAbove);
+        }
+
+        for (int i = 0; i < listenerUpdates.size(); i++) {
+            var updates = listenerUpdates.valueAt(i);
+            var listener = listenerUpdates.keyAt(i);
+            try {
+                listener.onTrustedPresentationChanged(updates.first.toArray(),
+                        updates.second.toArray());
+            } catch (RemoteException ignore) {
+            }
+        }
+    }
+
+    private void addListenerUpdate(
+            ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates,
+            ITrustedPresentationListener listener, int id, boolean presentationState) {
+        var updates = listenerUpdates.get(listener);
+        if (updates == null) {
+            updates = new Pair<>(new IntArray(), new IntArray());
+            listenerUpdates.put(listener, updates);
+        }
+        if (presentationState) {
+            updates.first.add(id);
+        } else {
+            updates.second.add(id);
+        }
+    }
+
+
+    private void checkIfInThreshold(
+            ArrayList<TrustedPresentationInfo> listeners,
+            ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates,
+            float fractionRendered, float alpha, long currTimeMs) {
+        ProtoLog.v(WM_DEBUG_TPL, "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d",
+                fractionRendered, alpha, currTimeMs);
+        for (int i = 0; i < listeners.size(); i++) {
+            var trustedPresentationInfo = listeners.get(i);
+            var listener = trustedPresentationInfo.mListener;
+            boolean lastState = trustedPresentationInfo.mLastComputedTrustedPresentationState;
+            boolean newState =
+                    (alpha >= trustedPresentationInfo.mThresholds.minAlpha) && (fractionRendered
+                            >= trustedPresentationInfo.mThresholds.minFractionRendered);
+            trustedPresentationInfo.mLastComputedTrustedPresentationState = newState;
+
+            ProtoLog.v(WM_DEBUG_TPL,
+                    "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f "
+                            + "minFractionRendered=%f",
+                    lastState, newState, alpha, trustedPresentationInfo.mThresholds.minAlpha,
+                    fractionRendered, trustedPresentationInfo.mThresholds.minFractionRendered);
+
+            if (lastState && !newState) {
+                // We were in the trusted presentation state, but now we left it,
+                // emit the callback if needed
+                if (trustedPresentationInfo.mLastReportedTrustedPresentationState) {
+                    trustedPresentationInfo.mLastReportedTrustedPresentationState = false;
+                    addListenerUpdate(listenerUpdates, listener,
+                            trustedPresentationInfo.mId, /*presentationState*/ false);
+                    ProtoLog.d(WM_DEBUG_TPL, "Adding untrusted state listener=%s with id=%d",
+                            listener, trustedPresentationInfo.mId);
+                }
+                // Reset the timer
+                trustedPresentationInfo.mEnteredTrustedPresentationStateTime = -1;
+            } else if (!lastState && newState) {
+                // We were not in the trusted presentation state, but we entered it, begin the timer
+                // and make sure this gets called at least once more!
+                trustedPresentationInfo.mEnteredTrustedPresentationStateTime = currTimeMs;
+                mHandler.postDelayed(() -> {
+                    computeTpl(mLastWindowHandles);
+                }, (long) (trustedPresentationInfo.mThresholds.stabilityRequirementMs * 1.5));
+            }
+
+            // Has the timer elapsed, but we are still in the state? Emit a callback if needed
+            if (!trustedPresentationInfo.mLastReportedTrustedPresentationState && newState && (
+                    currTimeMs - trustedPresentationInfo.mEnteredTrustedPresentationStateTime
+                            > trustedPresentationInfo.mThresholds.stabilityRequirementMs)) {
+                trustedPresentationInfo.mLastReportedTrustedPresentationState = true;
+                addListenerUpdate(listenerUpdates, listener,
+                        trustedPresentationInfo.mId, /*presentationState*/ true);
+                ProtoLog.d(WM_DEBUG_TPL, "Adding trusted state listener=%s with id=%d",
+                        listener, trustedPresentationInfo.mId);
+            }
+        }
+    }
+
+    private float computeFractionRendered(Region visibleRegion, RectF screenBounds,
+            Size contentSize,
+            float sx, float sy) {
+        ProtoLog.v(WM_DEBUG_TPL,
+                "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s "
+                        + "scale=%f,%f",
+                visibleRegion, screenBounds, contentSize, sx, sy);
+
+        if (contentSize.getWidth() == 0 || contentSize.getHeight() == 0) {
+            return -1;
+        }
+        if (screenBounds.width() == 0 || screenBounds.height() == 0) {
+            return -1;
+        }
+
+        float fractionRendered = Math.min(sx * sy, 1.0f);
+        ProtoLog.v(WM_DEBUG_TPL, "fractionRendered scale=%f", fractionRendered);
+
+        float boundsOverSourceW = screenBounds.width() / (float) contentSize.getWidth();
+        float boundsOverSourceH = screenBounds.height() / (float) contentSize.getHeight();
+        fractionRendered *= boundsOverSourceW * boundsOverSourceH;
+        ProtoLog.v(WM_DEBUG_TPL, "fractionRendered boundsOverSource=%f", fractionRendered);
+        // Compute the size of all the rects since they may be disconnected.
+        float[] visibleSize = new float[1];
+        RegionUtils.forEachRect(visibleRegion, rect -> {
+            float size = rect.width() * rect.height();
+            visibleSize[0] += size;
+        });
+
+        fractionRendered *= visibleSize[0] / (screenBounds.width() * screenBounds.height());
+        return fractionRendered;
+    }
+
+    private static class TrustedPresentationInfo {
+        boolean mLastComputedTrustedPresentationState = false;
+        boolean mLastReportedTrustedPresentationState = false;
+        long mEnteredTrustedPresentationStateTime = -1;
+        final TrustedPresentationThresholds mThresholds;
+
+        final ITrustedPresentationListener mListener;
+        final int mId;
+
+        private TrustedPresentationInfo(TrustedPresentationThresholds thresholds, int id,
+                ITrustedPresentationListener listener) {
+            mThresholds = thresholds;
+            mId = id;
+            mListener = listener;
+            checkValid(thresholds);
+        }
+
+        private void checkValid(TrustedPresentationThresholds thresholds) {
+            if (thresholds.minAlpha <= 0 || thresholds.minFractionRendered <= 0
+                    || thresholds.stabilityRequirementMs < 1) {
+                throw new IllegalArgumentException(
+                        "TrustedPresentationThresholds values are invalid");
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 72632dc..10dd334 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -303,9 +303,11 @@
 import android.window.ClientWindowFrames;
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
+import android.window.ITrustedPresentationListener;
 import android.window.ScreenCapture;
 import android.window.SystemPerformanceHinter;
 import android.window.TaskSnapshot;
+import android.window.TrustedPresentationThresholds;
 import android.window.WindowContainerToken;
 import android.window.WindowContextInfo;
 
@@ -764,6 +766,9 @@
     private final SurfaceSyncGroupController mSurfaceSyncGroupController =
             new SurfaceSyncGroupController();
 
+    final TrustedPresentationListenerController mTrustedPresentationListenerController =
+            new TrustedPresentationListenerController();
+
     @VisibleForTesting
     final class SettingsObserver extends ContentObserver {
         private final Uri mDisplayInversionEnabledUri =
@@ -7171,6 +7176,7 @@
                 pw.println(separator);
             }
             mSystemPerformanceHinter.dump(pw, "");
+            mTrustedPresentationListenerController.dump(pw);
         }
     }
 
@@ -7302,7 +7308,12 @@
         }
     }
 
-    MousePositionTracker mMousePositionTracker = new MousePositionTracker();
+    // The mouse position tracker will be obsolete after the Pointer Icon Refactor.
+    // TODO(b/293587049): Remove after the refactoring is fully rolled out.
+    @Nullable
+    final MousePositionTracker mMousePositionTracker =
+            com.android.input.flags.Flags.enablePointerChoreographer() ? null
+                    : new MousePositionTracker();
 
     private static class MousePositionTracker implements PointerEventListener {
         private boolean mLatestEventWasMouse;
@@ -7354,6 +7365,9 @@
     };
 
     void updatePointerIcon(IWindow client) {
+        if (mMousePositionTracker == null) {
+            return;
+        }
         int pointerDisplayId;
         float mouseX, mouseY;
 
@@ -7400,6 +7414,9 @@
     }
 
     void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
+        if (mMousePositionTracker == null) {
+            return;
+        }
         // Mouse position tracker has not been getting updates while dragging, update it now.
         if (!mMousePositionTracker.updatePosition(
                 displayContent.getDisplayId(), latestX, latestY)) {
@@ -7423,6 +7440,9 @@
         }
     }
     void setMousePointerDisplayId(int displayId) {
+        if (mMousePositionTracker == null) {
+            return;
+        }
         mMousePositionTracker.setPointerDisplayId(displayId);
     }
 
@@ -9771,4 +9791,17 @@
             Binder.restoreCallingIdentity(origId);
         }
     }
+
+    @Override
+    public void registerTrustedPresentationListener(IBinder window,
+            ITrustedPresentationListener listener,
+            TrustedPresentationThresholds thresholds, int id) {
+        mTrustedPresentationListenerController.registerListener(window, listener, thresholds, id);
+    }
+
+    @Override
+    public void unregisterTrustedPresentationListener(ITrustedPresentationListener listener,
+            int id) {
+        mTrustedPresentationListenerController.unregisterListener(listener, id);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b890a9e..4e9d23c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -178,6 +178,7 @@
 import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
 import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
 import static com.android.window.flags.Flags.secureWindowState;
 import static com.android.window.flags.Flags.surfaceTrustedOverlay;
 
@@ -668,6 +669,12 @@
     boolean mSeamlesslyRotated = false;
 
     /**
+     * Whether the IME insets have been consumed. If {@code true}, this window won't be able to
+     * receive visible IME insets; {@code false}, otherwise.
+     */
+    boolean mImeInsetsConsumed = false;
+
+    /**
      * The insets state of sources provided by windows above the current window.
      */
     final InsetsState mAboveInsetsState = new InsetsState();
@@ -1189,6 +1196,11 @@
             ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", this, parentWindow);
             parentWindow.addChild(this, sWindowSubLayerComparator);
         }
+
+        if (token.mRoundedCornerOverlay) {
+            mWmService.mTrustedPresentationListenerController.addIgnoredWindowTokens(
+                    getWindowToken());
+        }
     }
 
     @Override
@@ -1487,7 +1499,9 @@
 
             if (insetsChanged) {
                 mWindowFrames.setInsetsChanged(false);
-                mWmService.mWindowsInsetsChanged--;
+                if (mWmService.mWindowsInsetsChanged > 0) {
+                    mWmService.mWindowsInsetsChanged--;
+                }
                 if (mWmService.mWindowsInsetsChanged == 0) {
                     mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
                 }
@@ -2393,6 +2407,9 @@
         }
 
         mWmService.postWindowRemoveCleanupLocked(this);
+
+        mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
+                getWindowToken());
     }
 
     @Override
@@ -3796,13 +3813,15 @@
      */
     void notifyInsetsChanged() {
         ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsChanged for %s ", this);
-        mWindowFrames.setInsetsChanged(true);
+        if (!mWindowFrames.hasInsetsChanged()) {
+            mWindowFrames.setInsetsChanged(true);
 
-        // If the new InsetsState won't be dispatched before releasing WM lock, the following
-        // message will be executed.
-        mWmService.mWindowsInsetsChanged++;
-        mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
-        mWmService.mH.sendEmptyMessage(WindowManagerService.H.INSETS_CHANGED);
+            // If the new InsetsState won't be dispatched before releasing WM lock, the following
+            // message will be executed.
+            mWmService.mWindowsInsetsChanged++;
+            mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
+            mWmService.mH.sendEmptyMessage(WindowManagerService.H.INSETS_CHANGED);
+        }
 
         final WindowContainer p = getParent();
         if (p != null) {
@@ -4192,6 +4211,7 @@
         } else {
             pw.print("null");
         }
+        pw.println();
 
         if (mXOffset != 0 || mYOffset != 0) {
             pw.println(prefix + "mXOffset=" + mXOffset + " mYOffset=" + mYOffset);
@@ -4225,6 +4245,9 @@
         if (computeDragResizing()) {
             pw.println(prefix + "computeDragResizing=" + computeDragResizing());
         }
+        if (mImeInsetsConsumed) {
+            pw.println(prefix + "mImeInsetsConsumed=true");
+        }
         pw.println(prefix + "isOnScreen=" + isOnScreen());
         pw.println(prefix + "isVisible=" + isVisible());
         pw.println(prefix + "keepClearAreas: restricted=" + mKeepClearAreas
@@ -5185,9 +5208,13 @@
 
         boolean voteChanged = refreshRatePolicy.updateFrameRateVote(this);
         if (voteChanged) {
-            getPendingTransaction().setFrameRate(
-                    mSurfaceControl, mFrameRateVote.mRefreshRate,
-                    mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
+            getPendingTransaction()
+                    .setFrameRate(mSurfaceControl, mFrameRateVote.mRefreshRate,
+                        mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
+            if (explicitRefreshRateHints()) {
+                getPendingTransaction().setFrameRateSelectionStrategy(mSurfaceControl,
+                        mFrameRateVote.mSelectionStrategy);
+            }
 
         }
     }
diff --git a/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java b/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java
index 8c8f6a6..193a0c8 100644
--- a/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java
+++ b/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java
@@ -30,7 +30,7 @@
      * Set of DisplayInfo fields that are overridden in DisplayManager using values from
      * WindowManager
      */
-    public static final DisplayInfoFields WM_OVERRIDE_FIELDS = (out, source) -> {
+    public static final DisplayInfoFieldsUpdater WM_OVERRIDE_FIELDS = (out, source) -> {
         out.appWidth = source.appWidth;
         out.appHeight = source.appHeight;
         out.smallestNominalAppWidth = source.smallestNominalAppWidth;
@@ -55,7 +55,7 @@
     public static void copyDisplayInfoFields(@NonNull DisplayInfo out,
             @NonNull DisplayInfo base,
             @Nullable DisplayInfo override,
-            @NonNull DisplayInfoFields fields) {
+            @NonNull DisplayInfoFieldsUpdater fields) {
         out.copyFrom(base);
 
         if (override != null) {
@@ -66,7 +66,7 @@
     /**
      * Callback interface that allows to specify a subset of fields of DisplayInfo object
      */
-    public interface DisplayInfoFields {
+    public interface DisplayInfoFieldsUpdater {
         /**
          * Copies a subset of fields from {@param source} to {@param out}
          *
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 24ee163..b19f3d8 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -187,7 +187,7 @@
         "android.hardware.power.stats@1.0",
         "android.hardware.power.stats-V1-ndk",
         "android.hardware.thermal@1.0",
-        "android.hardware.thermal-V1-ndk",
+        "android.hardware.thermal-V2-ndk",
         "android.hardware.tv.input@1.0",
         "android.hardware.tv.input-V2-ndk",
         "android.hardware.vibrator-V2-cpp",
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index 44609ac..ea5fb5d6 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -304,7 +304,7 @@
         /** These permissions are supported for virtual devices. */
         // TODO: b/298661870 - Use new API to get the list of device aware permissions.
         val DEVICE_AWARE_PERMISSIONS =
-            if (Flags.deviceAwarePermissionApis()) {
+            if (Flags.deviceAwarePermissionApisEnabled()) {
                 setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
             } else {
                 emptySet<String>()
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index a7d3249..0f65494 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -1551,7 +1551,8 @@
         permissionName: String,
         deviceId: Int,
     ): Int {
-        return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) {
+        return if (!Flags.deviceAwarePermissionApisEnabled() ||
+            deviceId == Context.DEVICE_ID_DEFAULT) {
             with(policy) { getPermissionFlags(appId, userId, permissionName) }
         } else {
             if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) {
@@ -1586,7 +1587,8 @@
         deviceId: Int,
         flags: Int
     ): Boolean {
-        return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) {
+        return if (!Flags.deviceAwarePermissionApisEnabled() ||
+            deviceId == Context.DEVICE_ID_DEFAULT) {
             with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
         } else {
             if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index a0dc2b6..f07e820 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -817,12 +817,14 @@
 
         ps1.setUsesSdkLibraries(new String[] { "com.example.sdk.one" });
         ps1.setUsesSdkLibrariesVersionsMajor(new long[] { 12 });
+        ps1.setUsesSdkLibrariesOptional(new boolean[] {true});
         ps1.setFlags(ps1.getFlags() | ApplicationInfo.FLAG_SYSTEM);
         settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
         assertThat(settingsUnderTest.disableSystemPackageLPw(PACKAGE_NAME_1, false), is(true));
 
         ps2.setUsesSdkLibraries(new String[] { "com.example.sdk.two" });
         ps2.setUsesSdkLibrariesVersionsMajor(new long[] { 34 });
+        ps2.setUsesSdkLibrariesOptional(new boolean[] {false});
         settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
 
         settingsUnderTest.writeLPr(computer, /*sync=*/true);
@@ -838,19 +840,30 @@
         Truth.assertThat(readPs1).isNotNull();
         Truth.assertThat(readPs1.getUsesSdkLibraries()).isNotNull();
         Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).isNotNull();
+        Truth.assertThat(readPs1.getUsesSdkLibrariesOptional()).isNotNull();
         Truth.assertThat(readPs2).isNotNull();
         Truth.assertThat(readPs2.getUsesSdkLibraries()).isNotNull();
         Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).isNotNull();
+        Truth.assertThat(readPs2.getUsesSdkLibrariesOptional()).isNotNull();
 
         List<Long> ps1VersionsAsList = new ArrayList<>();
         for (long version : ps1.getUsesSdkLibrariesVersionsMajor()) {
             ps1VersionsAsList.add(version);
         }
 
+        List<Boolean> ps1RequireAsList = new ArrayList<>();
+        for (boolean optional : ps1.getUsesSdkLibrariesOptional()) {
+            ps1RequireAsList.add(optional);
+        }
+
         List<Long> ps2VersionsAsList = new ArrayList<>();
         for (long version : ps2.getUsesSdkLibrariesVersionsMajor()) {
             ps2VersionsAsList.add(version);
         }
+        List<Boolean> ps2RequireAsList = new ArrayList<>();
+        for (boolean optional : ps2.getUsesSdkLibrariesOptional()) {
+            ps2RequireAsList.add(optional);
+        }
 
         Truth.assertThat(readPs1.getUsesSdkLibraries()).asList()
                 .containsExactlyElementsIn(ps1.getUsesSdkLibraries()).inOrder();
@@ -858,11 +871,17 @@
         Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).asList()
                 .containsExactlyElementsIn(ps1VersionsAsList).inOrder();
 
+        Truth.assertThat(readPs1.getUsesSdkLibrariesOptional()).asList()
+                .containsExactlyElementsIn(ps1RequireAsList).inOrder();
+
         Truth.assertThat(readPs2.getUsesSdkLibraries()).asList()
                 .containsExactlyElementsIn(ps2.getUsesSdkLibraries()).inOrder();
 
         Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).asList()
                 .containsExactlyElementsIn(ps2VersionsAsList).inOrder();
+
+        Truth.assertThat(readPs2.getUsesSdkLibrariesOptional()).asList()
+                .containsExactlyElementsIn(ps2RequireAsList).inOrder();
     }
 
     @Test
@@ -1047,6 +1066,7 @@
                 UserManagerService.getInstance(),
                 null /*usesSdkLibraries*/,
                 null /*usesSdkLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -1087,6 +1107,7 @@
                 UserManagerService.getInstance(),
                 null /*usesSdkLibraries*/,
                 null /*usesSdkLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -1129,6 +1150,7 @@
                     UserManagerService.getInstance(),
                     null /*usesSdkLibraries*/,
                     null /*usesSdkLibrariesVersions*/,
+                    null /*usesSdkLibrariesOptional*/,
                     null /*usesStaticLibraries*/,
                     null /*usesStaticLibrariesVersions*/,
                     null /*mimeGroups*/,
@@ -1167,6 +1189,7 @@
                 UserManagerService.getInstance(),
                 null /*usesSdkLibraries*/,
                 null /*usesSdkLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -1214,6 +1237,7 @@
                 UserManagerService.getInstance(),
                 null /*usesSdkLibraries*/,
                 null /*usesSdkLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -1263,6 +1287,7 @@
                 null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
                 null /*mimeGroups*/,
                 UUID.randomUUID(),
                 34 /*targetSdkVersion*/,
@@ -1311,6 +1336,7 @@
                 null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
                 null /*mimeGroups*/,
                 UUID.randomUUID(),
                 34 /*targetSdkVersion*/,
@@ -1356,6 +1382,7 @@
                 null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
                 null /*mimeGroups*/,
                 UUID.randomUUID(),
                 34 /*targetSdkVersion*/,
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index ea88ec2..a62cd4f 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -1062,7 +1062,7 @@
                 .addProtectedBroadcast("foo8")
                 .setSdkLibraryName("sdk12")
                 .setSdkLibVersionMajor(42)
-                .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"})
+                .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"}, true)
                 .setStaticSharedLibraryName("foo23")
                 .setStaticSharedLibraryVersion(100)
                 .addUsesStaticLibrary("foo23", 100, new String[]{"digest"})
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index decb44c..6202908 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -599,8 +599,8 @@
                 .setVolumeUuid(UUID_ONE.toString())
                 .addUsesStaticLibrary("some.static.library", 234L, new String[]{"testCert1"})
                 .addUsesStaticLibrary("some.other.static.library", 456L, new String[]{"testCert2"})
-                .addUsesSdkLibrary("some.sdk.library", 123L, new String[]{"testCert3"})
-                .addUsesSdkLibrary("some.other.sdk.library", 789L, new String[]{"testCert4"})
+                .addUsesSdkLibrary("some.sdk.library", 123L, new String[]{"testCert3"}, false)
+                .addUsesSdkLibrary("some.other.sdk.library", 789L, new String[]{"testCert4"}, true)
                 .hideAsParsed())
                 .setNativeLibraryRootDir("/data/tmp/randompath/base.apk:/lib")
                 .setVersionCodeMajor(1)
@@ -628,6 +628,7 @@
         assertThat(pkgSetting.getUsesSdkLibraries(),
                 arrayContaining("some.sdk.library", "some.other.sdk.library"));
         assertThat(pkgSetting.getUsesSdkLibrariesVersionsMajor(), is(new long[]{123L, 789L}));
+        assertThat(pkgSetting.getUsesSdkLibrariesOptional(), is(new boolean[]{false, true}));
         assertThat(pkgSetting.getPkg(), is(scanResult.mRequest.mParsedPackage));
         assertThat(pkgSetting.getPath(), is(new File(createCodePath(packageName))));
         assertThat(pkgSetting.getVersionCode(),
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 170faf6..09b66c1 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -127,6 +127,7 @@
         "getUsesSdkLibraries",
         "getUsesSdkLibrariesVersionsMajor",
         "getUsesSdkLibrariesCertDigests",
+        "getUsesSdkLibrariesOptional",
         // Tested through addUsesStaticLibrary
         "addUsesStaticLibrary",
         "getUsesStaticLibraries",
@@ -608,7 +609,7 @@
         .setSplitHasCode(1, false)
         .setSplitClassLoaderName(0, "testSplitClassLoaderNameZero")
         .setSplitClassLoaderName(1, "testSplitClassLoaderNameOne")
-        .addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1"))
+        .addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1"), true)
         .addUsesStaticLibrary("testStatic", 3L, arrayOf("testCertDigest2"))
 
     override fun finalizeObject(parcelable: Parcelable) {
@@ -661,6 +662,7 @@
         expect.that(after.usesSdkLibrariesCertDigests!!.size).isEqualTo(1)
         expect.that(after.usesSdkLibrariesCertDigests!![0]).asList()
                 .containsExactly("testCertDigest1")
+        expect.that(after.usesSdkLibrariesOptional).asList().containsExactly(true)
 
         expect.that(after.usesStaticLibraries).containsExactly("testStatic")
         expect.that(after.usesStaticLibrariesVersions).asList().containsExactly(3L)
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index a250ac7..0702764 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -368,10 +368,15 @@
         }
     }
 
+    private JobInfo.Builder createJobInfoBuilder(int jobId) {
+        return new JobInfo.Builder(jobId, new ComponentName(mContext, "TestQuotaJobService"));
+    }
+
     private JobStatus createJobStatus(String testTag, int jobId) {
-        JobInfo jobInfo = new JobInfo.Builder(jobId,
-                new ComponentName(mContext, "TestQuotaJobService"))
-                .build();
+        return createJobStatus(testTag, createJobInfoBuilder(jobId).build());
+    }
+
+    private JobStatus createJobStatus(String testTag, JobInfo jobInfo) {
         return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo);
     }
 
@@ -1333,39 +1338,70 @@
         mQuotaController.saveTimingSession(0, SOURCE_PACKAGE,
                 createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
                         3 * MINUTE_IN_MILLIS, 5), false);
+        final long timeUntilQuotaConsumedMs = 7 * MINUTE_IN_MILLIS;
         JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked", 0);
+        //noinspection deprecation
+        JobStatus jobDefIWF = createJobStatus("testGetMaxJobExecutionTimeLocked",
+                createJobInfoBuilder(1)
+                        .setImportantWhileForeground(true)
+                        .setPriority(JobInfo.PRIORITY_DEFAULT)
+                        .build());
+        JobStatus jobHigh = createJobStatus("testGetMaxJobExecutionTimeLocked",
+                createJobInfoBuilder(2).setPriority(JobInfo.PRIORITY_HIGH).build());
         setStandbyBucket(RARE_INDEX, job);
+        setStandbyBucket(RARE_INDEX, jobDefIWF);
+        setStandbyBucket(RARE_INDEX, jobHigh);
 
         setCharging();
         synchronized (mQuotaController.mLock) {
             assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
         }
 
         setDischarging();
         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         synchronized (mQuotaController.mLock) {
-            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+            assertEquals(timeUntilQuotaConsumedMs,
                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
         }
 
         // Top-started job
         setProcessState(ActivityManager.PROCESS_STATE_TOP);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStartTrackingJobLocked(job, null);
+            trackJobs(job, jobDefIWF, jobHigh);
             mQuotaController.prepareForExecutionLocked(job);
+            mQuotaController.prepareForExecutionLocked(jobDefIWF);
+            mQuotaController.prepareForExecutionLocked(jobHigh);
         }
         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
         synchronized (mQuotaController.mLock) {
-            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+            assertEquals(timeUntilQuotaConsumedMs,
                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
             mQuotaController.maybeStopTrackingJobLocked(job, null);
+            mQuotaController.maybeStopTrackingJobLocked(jobDefIWF, null);
+            mQuotaController.maybeStopTrackingJobLocked(jobHigh, null);
         }
 
         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
         synchronized (mQuotaController.mLock) {
-            assertEquals(7 * MINUTE_IN_MILLIS,
+            assertEquals(timeUntilQuotaConsumedMs,
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+            assertEquals(timeUntilQuotaConsumedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(jobDefIWF));
+            assertEquals(timeUntilQuotaConsumedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(jobHigh));
         }
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
index cf81f0a..e131a98 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
@@ -16,12 +16,14 @@
 
 package com.android.server.pm
 
+import android.app.AppOpsManager
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.os.Binder
 import com.android.server.testutils.any
 import com.android.server.testutils.eq
 import com.android.server.testutils.nullable
+import com.android.server.testutils.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -31,6 +33,7 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 
+
 @RunWith(JUnit4::class)
 class DistractingPackageHelperTest : PackageHelperTestBase() {
 
@@ -40,6 +43,9 @@
         super.setup()
         distractingPackageHelper = DistractingPackageHelper(
                 pms, broadcastHelper, suspendPackageHelper)
+        whenever(rule.mocks().appOpsManager.checkOpNoThrow(
+                eq(AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION), any(), any()))
+                .thenReturn(AppOpsManager.MODE_DEFAULT)
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 46806f3..28bd987 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -15,6 +15,7 @@
  */
 package com.android.server.pm
 
+import android.app.AppOpsManager
 import android.app.PropertyInvalidatedCache
 import android.content.Context
 import android.content.Intent
@@ -151,7 +152,7 @@
                 .mockStatic(EventLog::class.java)
                 .mockStatic(LocalServices::class.java)
                 .mockStatic(LocalManagerRegistry::class.java)
-                .mockStatic(DeviceConfig::class.java)
+                .mockStatic(DeviceConfig::class.java, Mockito.CALLS_REAL_METHODS)
                 .mockStatic(HexEncoding::class.java)
                 .apply(withSession)
         session = apply.startMocking()
@@ -192,6 +193,7 @@
         val userManagerService: UserManagerService = mock()
         val componentResolver: ComponentResolver = mock()
         val permissionManagerInternal: PermissionManagerServiceInternal = mock()
+        val appOpsManager: AppOpsManager = mock()
         val incrementalManager: IncrementalManager = mock()
         val platformCompat: PlatformCompat = mock()
         val settings: Settings = mock()
@@ -304,6 +306,7 @@
         whenever(mocks.injector.defaultAppProvider) { mocks.defaultAppProvider }
         whenever(mocks.injector.backgroundHandler) { mocks.backgroundHandler }
         whenever(mocks.injector.updateOwnershipHelper) { mocks.updateOwnershipHelper }
+        whenever(mocks.injector.getSystemService(AppOpsManager::class.java)) { mocks.appOpsManager }
         wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig)
         whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP)
         whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index 7b381ce..1473033 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -16,12 +16,14 @@
 
 package com.android.server.pm
 
+import android.app.AppOpsManager
 import android.content.Intent
 import android.content.pm.SuspendDialogInfo
 import android.os.Binder
 import android.os.PersistableBundle
 import com.android.server.testutils.any
 import com.android.server.testutils.eq
+import com.android.server.testutils.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -32,6 +34,12 @@
 
 @RunWith(JUnit4::class)
 class SuspendPackageHelperTest : PackageHelperTestBase() {
+    override fun setup() {
+        super.setup()
+        whenever(rule.mocks().appOpsManager.checkOpNoThrow(
+                eq(AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION), any(), any()))
+                .thenReturn(AppOpsManager.MODE_DEFAULT)
+    }
 
     @Test
     fun setPackagesSuspended() {
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index f02e5a5..07197b1 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -12,6 +12,7 @@
     ],
 
     exclude_srcs: [
+        "src/com/android/server/power/stats/MultiStateStatsTest.java",
         "src/com/android/server/power/stats/PowerStatsStoreTest.java",
     ],
 
@@ -62,13 +63,12 @@
     static_libs: [
         "services.core",
         "modules-utils-binary-xml",
-
         "androidx.annotation_annotation",
         "androidx.test.rules",
     ],
     srcs: [
+        "src/com/android/server/power/stats/MultiStateStatsTest.java",
         "src/com/android/server/power/stats/PowerStatsStoreTest.java",
     ],
-    sdk_version: "test_current",
     auto_gen_config: true,
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
index 48e2dd7..6d61dc8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
@@ -26,8 +26,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.MultiStateStats;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
index 8ca4ff6..993d834 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
@@ -38,7 +38,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
index eb03a6c..e8f46b3 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -22,12 +22,12 @@
 import static org.junit.Assert.assertThrows;
 
 import android.os.BatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.MultiStateStats;
-
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -39,6 +39,10 @@
 @SmallTest
 public class MultiStateStatsTest {
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .build();
+
     public static final int DIMENSION_COUNT = 2;
 
     @Test
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index 9b047f2..6537d47 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -40,6 +40,7 @@
             mediaSharedWithParent='true'
             credentialShareableWithParent='false'
             authAlwaysRequiredToDisableQuietMode='true'
+            allowStoppingUserWithDelayedLocking='true'
             showInSettings='23'
             hideInSettingsInQuietMode='true'
             inheritDevicePolicy='450'
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index a9967f6..71d64cf 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -532,12 +532,11 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
-    public void testTwoFingerTripleTap_StateIsIdle_shouldInActivated() {
+    public void testTwoFingerDoubleTap_StateIsIdle_shouldInActivated() {
         goFromStateIdleTo(STATE_IDLE);
 
         twoFingerTap();
         twoFingerTap();
-        twoFingerTap();
 
         assertIn(STATE_ACTIVATED);
         verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
@@ -546,13 +545,12 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
-    public void testTwoFingerTripleTap_StateIsActivated_shouldInIdle() {
+    public void testTwoFingerDoubleTap_StateIsActivated_shouldInIdle() {
         goFromStateIdleTo(STATE_ACTIVATED);
         reset(mMockMagnificationLogger);
 
         twoFingerTap();
         twoFingerTap();
-        twoFingerTap();
 
         assertIn(STATE_IDLE);
         verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
@@ -561,11 +559,10 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
-    public void testTwoFingerTripleTapAndHold_StateIsIdle_shouldZoomsImmediately() {
+    public void testTwoFingerDoubleTapAndHold_StateIsIdle_shouldZoomsImmediately() {
         goFromStateIdleTo(STATE_IDLE);
 
         twoFingerTap();
-        twoFingerTap();
         twoFingerTapAndHold();
 
         assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP);
@@ -575,11 +572,10 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
-    public void testTwoFingerTripleSwipeAndHold_StateIsIdle_shouldZoomsImmediately() {
+    public void testTwoFingerDoubleSwipeAndHold_StateIsIdle_shouldZoomsImmediately() {
         goFromStateIdleTo(STATE_IDLE);
 
         twoFingerTap();
-        twoFingerTap();
         twoFingerSwipeAndHold();
 
         assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
index a3b67ae..c99e040 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
@@ -72,15 +72,15 @@
     public static final int STATE_SHOW_MAGNIFIER_TRIPLE_TAP = 4;
     public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 5;
     public static final int STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 6;
-    public static final int STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP = 7;
-    public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD = 8;
-    public static final int STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD = 9;
+    public static final int STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP = 7;
+    public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD = 8;
+    public static final int STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD = 9;
     //TODO: Test it after can injecting Handler to GestureMatcher is available.
 
     public static final int FIRST_STATE = STATE_IDLE;
     public static final int LAST_STATE = STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD;
     public static final int LAST_STATE_WITH_MULTI_FINGER =
-            STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD;
+            STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD;
 
     // Co-prime x and y, to potentially catch x-y-swapped errors
     public static final float DEFAULT_TAP_X = 301;
@@ -257,15 +257,15 @@
             break;
             case STATE_SHOW_MAGNIFIER_SHORTCUT:
             case STATE_SHOW_MAGNIFIER_TRIPLE_TAP:
-            case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP:
+            case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP:
                 check(isWindowMagnifierEnabled(DISPLAY_0), state);
                 check(mWindowMagnificationGestureHandler.mCurrentState
                         == mWindowMagnificationGestureHandler.mDetectingState, state);
                 break;
             case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
             case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
-            case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD:
-            case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: {
+            case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD:
+            case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: {
                 check(isWindowMagnifierEnabled(DISPLAY_0), state);
                 check(mWindowMagnificationGestureHandler.mCurrentState
                         == mWindowMagnificationGestureHandler.mViewportDraggingState, state);
@@ -337,8 +337,7 @@
                     tapAndHold();
                 }
                 break;
-                case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: {
-                    twoFingerTap();
+                case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP: {
                     twoFingerTap();
                     twoFingerTap();
                     // Wait for two-finger tap gesture completed.
@@ -346,17 +345,15 @@
                     InstrumentationRegistry.getInstrumentation().waitForIdleSync();
                 }
                 break;
-                case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: {
-                    twoFingerTap();
+                case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: {
                     twoFingerTap();
                     twoFingerTapAndHold();
                 }
                 break;
-                case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: {
+                case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: {
                     // enabled then perform two finger triple tap and hold gesture
                     goFromStateIdleTo(STATE_SHOW_MAGNIFIER_SHORTCUT);
                     twoFingerTap();
-                    twoFingerTap();
                     twoFingerTapAndHold();
                 }
                 break;
@@ -394,16 +391,15 @@
             }
             break;
             case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
-            case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD:
+            case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD:
                 send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
                 break;
             case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
-            case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD:
+            case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD:
                 send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
                 returnToNormalFrom(STATE_SHOW_MAGNIFIER_SHORTCUT);
                 break;
-            case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: {
-                twoFingerTap();
+            case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP: {
                 twoFingerTap();
                 twoFingerTap();
                 // Wait for two-finger tap gesture completed.
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 d26d671..77b1455 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -92,6 +92,7 @@
 import android.os.UserManager;
 import android.os.storage.IStorageManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.Log;
 import android.view.Display;
 
@@ -104,11 +105,14 @@
 import com.android.server.pm.UserJourneyLogger;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
+import com.android.server.pm.UserTypeDetails;
+import com.android.server.pm.UserTypeFactory;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerService;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
@@ -175,6 +179,9 @@
             USER_START_MSG,
             REPORT_LOCKED_BOOT_COMPLETE_MSG);
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         runWithDexmakerShareClassLoader(() -> {
@@ -789,30 +796,101 @@
     }
 
     @Test
-    public void testStartProfile() throws Exception {
-        setUpAndStartProfileInBackground(TEST_USER_ID1);
+    public void testStartManagedProfile() throws Exception {
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
 
         startBackgroundUserAssertions();
         verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
     }
 
     @Test
-    public void testStartProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception {
+    public void testStartManagedProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception {
         mockIsUsersOnSecondaryDisplaysEnabled(true);
 
-        setUpAndStartProfileInBackground(TEST_USER_ID1);
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
 
         startBackgroundUserAssertions();
         verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
     }
 
     @Test
-    public void testStopProfile() throws Exception {
-        setUpAndStartProfileInBackground(TEST_USER_ID1);
+    public void testStopManagedProfile() throws Exception {
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
         assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true);
         verifyUserUnassignedFromDisplay(TEST_USER_ID1);
     }
 
+    @Test
+    public void testStopPrivateProfile() throws Exception {
+        mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true);
+        verifyUserUnassignedFromDisplay(TEST_USER_ID1);
+
+        mSetFlagsRule.disableFlags(
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* expectLocking= */ true);
+        verifyUserUnassignedFromDisplay(TEST_USER_ID2);
+    }
+
+    @Test
+    public void testStopPrivateProfileWithDelayedLocking() throws Exception {
+        mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ false);
+    }
+
+    @Test
+    public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception {
+        mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.disableFlags(
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ true);
+
+        mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ true);
+    }
+
+    @Test
+    public void testStopPrivateProfileWithDelayedLocking_maxRunningUsersBreached()
+            throws Exception {
+        mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+                /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ true);
+    }
+
+    @Test
+    public void testStopManagedProfileWithDelayedLocking() throws Exception {
+        mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ true);
+    }
+
     /** Tests handleIncomingUser() for a variety of permissions and situations. */
     @Test
     public void testHandleIncomingUser() throws Exception {
@@ -1001,8 +1079,8 @@
         mUserStates.put(userId, mUserController.getStartedUserState(userId));
     }
 
-    private void setUpAndStartProfileInBackground(int userId) throws Exception {
-        setUpUser(userId, UserInfo.FLAG_PROFILE, false, UserManager.USER_TYPE_PROFILE_MANAGED);
+    private void setUpAndStartProfileInBackground(int userId, String userType) throws Exception {
+        setUpUser(userId, UserInfo.FLAG_PROFILE, false, userType);
         assertThat(mUserController.startProfile(userId, /* evenWhenDisabled=*/ false,
                 /* unlockListener= */ null)).isTrue();
 
@@ -1070,6 +1148,11 @@
         userInfo.preCreated = preCreated;
         when(mInjector.mUserManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo);
         when(mInjector.mUserManagerMock.isPreCreated(userId)).thenReturn(preCreated);
+
+        UserTypeDetails userTypeDetails = UserTypeFactory.getUserTypes().get(userType);
+        assertThat(userTypeDetails).isNotNull();
+        when(mInjector.mUserManagerInternalMock.getUserProperties(eq(userId)))
+                .thenReturn(userTypeDetails.getDefaultUserPropertiesReference());
     }
 
     private static List<String> getActions(List<Intent> intents) {
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index cc3c880..f1c1dc3 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -266,18 +266,22 @@
                     .adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED);
 
             // no metadata set
-            assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
-                    DEVICE_TYPE_DEFAULT.getBytes()));
-            assertFalse(
-                    mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
-                            mFakeBtDevice.getAddress()));
+            if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+                    DEVICE_TYPE_DEFAULT.getBytes())) {
+                assertFalse(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
+                        mFakeBtDevice.getAddress()));
+            }
 
             // metadata set
-            assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
-                    DEVICE_TYPE_HEADSET.getBytes()));
-            assertTrue(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
-                    mFakeBtDevice.getAddress()));
+            if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+                    DEVICE_TYPE_HEADSET.getBytes())) {
+                assertTrue(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
+                        mFakeBtDevice.getAddress()));
+            }
         } finally {
+            // reset the metadata device type
+            mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+                    DEVICE_TYPE_DEFAULT.getBytes());
             InstrumentationRegistry.getInstrumentation().getUiAutomation()
                     .dropShellPermissionIdentity();
         }
@@ -304,25 +308,30 @@
                     .adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED);
 
             // no metadata set
-            assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
-                    DEVICE_TYPE_DEFAULT.getBytes()));
-            assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER,
-                    mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
-                            mFakeBtDevice.getAddress()));
-            verify(mMockAudioService,
-                    timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState(
-                    eq(devState));
+            if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+                    DEVICE_TYPE_DEFAULT.getBytes())) {
+                assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER,
+                        mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
+                                mFakeBtDevice.getAddress()));
+                verify(mMockAudioService,
+                        timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState(
+                        eq(devState));
+            }
 
             // metadata set
-            assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
-                    DEVICE_TYPE_HEADSET.getBytes()));
-            assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES,
-                    mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
-                            mFakeBtDevice.getAddress()));
-            verify(mMockAudioService,
-                    timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState(
-                    any());
+            if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+                    DEVICE_TYPE_HEADSET.getBytes())) {
+                assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES,
+                        mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
+                                mFakeBtDevice.getAddress()));
+                verify(mMockAudioService,
+                        timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState(
+                        any());
+            }
         } finally {
+            // reset the metadata device type
+            mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+                    DEVICE_TYPE_DEFAULT.getBytes());
             InstrumentationRegistry.getInstrumentation().getUiAutomation()
                     .dropShellPermissionIdentity();
         }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index 3355910..18e6f0a 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -151,6 +151,19 @@
     }
 
     @Test
+    public void userNotAllowlisted_launchIsBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithNoAllowedUsers();
+        gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
     public void openNonBlockedAppOnVirtualDisplay_isNotBlocked() {
         GenericWindowPolicyController gwpc = createGwpc();
         gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
@@ -702,6 +715,26 @@
                 /* customHomeComponent= */ null);
     }
 
+    private GenericWindowPolicyController createGwpcWithNoAllowedUsers() {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ new ArraySet<>(),
+                /* crossTaskNavigationAllowedByDefault= */ true,
+                /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ mSecureWindowCallback,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
     private GenericWindowPolicyController createGwpcWithCustomHomeComponent(
             ComponentName homeComponent) {
         return new GenericWindowPolicyController(
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 72cc969..d7ed7c2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -69,6 +69,7 @@
                 .setMediaSharedWithParent(false)
                 .setCredentialShareableWithParent(true)
                 .setAuthAlwaysRequiredToDisableQuietMode(false)
+                .setAllowStoppingUserWithDelayedLocking(false)
                 .setDeleteAppWithParent(false)
                 .setAlwaysVisible(false)
                 .setCrossProfileContentSharingStrategy(0)
@@ -85,6 +86,7 @@
         actualProps.setMediaSharedWithParent(true);
         actualProps.setCredentialShareableWithParent(false);
         actualProps.setAuthAlwaysRequiredToDisableQuietMode(true);
+        actualProps.setAllowStoppingUserWithDelayedLocking(true);
         actualProps.setDeleteAppWithParent(true);
         actualProps.setAlwaysVisible(true);
         actualProps.setCrossProfileContentSharingStrategy(1);
@@ -130,6 +132,7 @@
                 .setMediaSharedWithParent(true)
                 .setDeleteAppWithParent(true)
                 .setAuthAlwaysRequiredToDisableQuietMode(false)
+                .setAllowStoppingUserWithDelayedLocking(false)
                 .setAlwaysVisible(true)
                 .build();
         final UserProperties orig = new UserProperties(defaultProps);
@@ -139,6 +142,7 @@
         orig.setInheritDevicePolicy(9456);
         orig.setDeleteAppWithParent(false);
         orig.setAuthAlwaysRequiredToDisableQuietMode(true);
+        orig.setAllowStoppingUserWithDelayedLocking(true);
         orig.setAlwaysVisible(false);
 
         // Test every permission level. (Currently, it's linear so it's easy.)
@@ -184,6 +188,8 @@
         assertEqualGetterOrThrows(orig::getDeleteAppWithParent,
                 copy::getDeleteAppWithParent, exposeAll);
         assertEqualGetterOrThrows(orig::getAlwaysVisible, copy::getAlwaysVisible, exposeAll);
+        assertEqualGetterOrThrows(orig::getAllowStoppingUserWithDelayedLocking,
+                copy::getAllowStoppingUserWithDelayedLocking, exposeAll);
 
         // Items requiring hasManagePermission - put them here using hasManagePermission.
         assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
@@ -258,6 +264,8 @@
                 .isEqualTo(actual.isCredentialShareableWithParent());
         assertThat(expected.isAuthAlwaysRequiredToDisableQuietMode())
                 .isEqualTo(actual.isAuthAlwaysRequiredToDisableQuietMode());
+        assertThat(expected.getAllowStoppingUserWithDelayedLocking())
+                .isEqualTo(actual.getAllowStoppingUserWithDelayedLocking());
         assertThat(expected.getDeleteAppWithParent()).isEqualTo(actual.getDeleteAppWithParent());
         assertThat(expected.getAlwaysVisible()).isEqualTo(actual.getAlwaysVisible());
         assertThat(expected.getCrossProfileContentSharingStrategy())
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index d0ad573..7083706 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -90,6 +90,7 @@
                 .setMediaSharedWithParent(true)
                 .setCredentialShareableWithParent(false)
                 .setAuthAlwaysRequiredToDisableQuietMode(true)
+                .setAllowStoppingUserWithDelayedLocking(true)
                 .setShowInSettings(900)
                 .setShowInSharingSurfaces(20)
                 .setShowInQuietMode(30)
@@ -167,6 +168,8 @@
         assertFalse(type.getDefaultUserPropertiesReference().isCredentialShareableWithParent());
         assertTrue(type.getDefaultUserPropertiesReference()
                 .isAuthAlwaysRequiredToDisableQuietMode());
+        assertTrue(type.getDefaultUserPropertiesReference()
+                .getAllowStoppingUserWithDelayedLocking());
         assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings());
         assertEquals(20, type.getDefaultUserPropertiesReference().getShowInSharingSurfaces());
         assertEquals(30,
@@ -322,6 +325,7 @@
                 .setMediaSharedWithParent(false)
                 .setCredentialShareableWithParent(true)
                 .setAuthAlwaysRequiredToDisableQuietMode(false)
+                .setAllowStoppingUserWithDelayedLocking(false)
                 .setShowInSettings(20)
                 .setInheritDevicePolicy(21)
                 .setShowInSharingSurfaces(22)
@@ -367,6 +371,8 @@
                 .isCredentialShareableWithParent());
         assertFalse(aospType.getDefaultUserPropertiesReference()
                 .isAuthAlwaysRequiredToDisableQuietMode());
+        assertFalse(aospType.getDefaultUserPropertiesReference()
+                .getAllowStoppingUserWithDelayedLocking());
         assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings());
         assertEquals(21, aospType.getDefaultUserPropertiesReference()
                 .getInheritDevicePolicy());
@@ -420,6 +426,8 @@
                 .isCredentialShareableWithParent());
         assertTrue(aospType.getDefaultUserPropertiesReference()
                 .isAuthAlwaysRequiredToDisableQuietMode());
+        assertTrue(aospType.getDefaultUserPropertiesReference()
+                .getAllowStoppingUserWithDelayedLocking());
         assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings());
         assertEquals(22,
                 aospType.getDefaultUserPropertiesReference().getShowInSharingSurfaces());
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index ced0bb5..a743fff 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -342,8 +342,12 @@
         assertThat(typeProps.getCrossProfileContentSharingStrategy())
                 .isEqualTo(privateProfileUserProperties.getCrossProfileContentSharingStrategy());
         assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent);
+        assertThrows(SecurityException.class,
+                privateProfileUserProperties::getAllowStoppingUserWithDelayedLocking);
+
         compareDrawables(mUserManager.getUserBadge(),
                 Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
+
         // Verify private profile parent
         assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
         UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
index 330dbb8..861d14a 100644
--- a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
@@ -30,13 +30,19 @@
 import com.android.internal.annotations.GuardedBy;
 
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @Presubmit
+@RunWith(Parameterized.class)
 public class AnrTimerTest {
 
     // The commonly used message timeout key.
@@ -106,6 +112,16 @@
     }
 
     /**
+     * Force AnrTimer to use the test parameter for the feature flag.
+     */
+    class TestInjector extends AnrTimer.Injector {
+        @Override
+        boolean anrTimerServiceEnabled() {
+            return mEnabled;
+        }
+    }
+
+    /**
      * An instrumented AnrTimer.
      */
     private static class TestAnrTimer extends AnrTimer<TestArg> {
@@ -137,6 +153,17 @@
         assertEquals(actual.what, MSG_TIMEOUT);
     }
 
+    @Parameters(name = "featureEnabled={0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] { {false}, {true} });
+    }
+
+    /** True if the feature is enabled. */
+    private boolean mEnabled;
+
+    public AnrTimerTest(boolean featureEnabled) {
+        mEnabled = featureEnabled;
+    }
 
     /**
      * Verify that a simple expiration succeeds.  The timer is started for 10ms.  The test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 5febd02..8936dc6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -16,13 +16,28 @@
 
 package com.android.server.notification;
 
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 import android.app.UiModeManager;
 import android.app.WallpaperManager;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.display.ColorDisplayManager;
 import android.os.PowerManager;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -36,6 +51,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -55,7 +71,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mContext = new TestableContext(InstrumentationRegistry.getContext(), null);
+        mContext = spy(new TestableContext(InstrumentationRegistry.getContext(), null));
         mContext.addMockSystemService(PowerManager.class, mPowerManager);
         mContext.addMockSystemService(ColorDisplayManager.class, mColorDisplayManager);
         mContext.addMockSystemService(UiModeManager.class, mUiModeManager);
@@ -74,25 +90,33 @@
                 .setShouldDisplayGrayscale(true)
                 .setShouldUseNightMode(true)
                 .build();
-        mApplier.apply(effects);
+        mApplier.apply(effects, UPDATE_ORIGIN_USER);
 
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
         verify(mColorDisplayManager).setSaturationLevel(eq(0));
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
-        verifyZeroInteractions(mUiModeManager); // Coming later; adding now so test fails then. :)
+        verify(mUiModeManager).setNightModeActivatedForCustomMode(
+                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
     }
 
     @Test
-    public void apply_removesEffects() {
+    public void apply_removesPreviouslyAppliedEffects() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
 
+        ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
+                .setShouldSuppressAmbientDisplay(true)
+                .setShouldDimWallpaper(true)
+                .build();
+        mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
+        verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+        verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+
         ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
-        mApplier.apply(noEffects);
+        mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
 
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
-        verify(mColorDisplayManager).setSaturationLevel(eq(100));
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
-        verifyZeroInteractions(mUiModeManager);
+        verifyZeroInteractions(mColorDisplayManager, mUiModeManager);
     }
 
     @Test
@@ -107,9 +131,104 @@
                 .setShouldDisplayGrayscale(true)
                 .setShouldUseNightMode(true)
                 .build();
-        mApplier.apply(effects);
+        mApplier.apply(effects, UPDATE_ORIGIN_USER);
 
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
         // (And no crash from missing services).
     }
+
+    @Test
+    public void apply_someEffects_onlyThoseEffectsApplied() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+                .setShouldDimWallpaper(true)
+                .setShouldDisplayGrayscale(true)
+                .build();
+        mApplier.apply(effects, UPDATE_ORIGIN_USER);
+
+        verify(mColorDisplayManager).setSaturationLevel(eq(0));
+        verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+
+        verify(mPowerManager, never()).suppressAmbientDisplay(anyString(), anyBoolean());
+        verify(mUiModeManager, never()).setNightModeActivatedForCustomMode(anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void apply_onlyEffectDeltaApplied() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        mApplier.apply(new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build(),
+                UPDATE_ORIGIN_USER);
+        verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+
+        // Apply a second effect and remove the first one.
+        mApplier.apply(new ZenDeviceEffects.Builder().setShouldDisplayGrayscale(true).build(),
+                UPDATE_ORIGIN_USER);
+
+        // Wallpaper dimming was undone, Grayscale was applied, nothing else was touched.
+        verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
+        verify(mColorDisplayManager).setSaturationLevel(eq(0));
+        verifyZeroInteractions(mPowerManager);
+        verifyZeroInteractions(mUiModeManager);
+    }
+
+    @Test
+    public void apply_darkThemeFromApp_appliedOnScreenOff() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        ArgumentCaptor<IntentFilter> intentFilterCaptor =
+                ArgumentCaptor.forClass(IntentFilter.class);
+
+        when(mPowerManager.isInteractive()).thenReturn(true);
+
+        mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
+                UPDATE_ORIGIN_APP);
+
+        // Effect was not yet applied, but a broadcast receiver was registered.
+        verifyZeroInteractions(mUiModeManager);
+        verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(),
+                intentFilterCaptor.capture(), anyInt());
+        assertThat(intentFilterCaptor.getValue().getAction(0)).isEqualTo(Intent.ACTION_SCREEN_OFF);
+        BroadcastReceiver screenOffReceiver = broadcastReceiverCaptor.getValue();
+
+        // Now the "screen off" event comes.
+        screenOffReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+
+        // So the effect is applied, and we stopped listening for this event.
+        verify(mUiModeManager).setNightModeActivatedForCustomMode(
+                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+        verify(mContext).unregisterReceiver(eq(screenOffReceiver));
+    }
+
+    @Test
+    public void apply_darkThemeFromAppWithScreenOff_appliedImmediately() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        when(mPowerManager.isInteractive()).thenReturn(false);
+
+        mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
+                UPDATE_ORIGIN_APP);
+
+        // Effect was applied, and no broadcast receiver was registered.
+        verify(mUiModeManager).setNightModeActivatedForCustomMode(
+                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+        verify(mContext, never()).registerReceiver(any(), any(), anyInt());
+    }
+
+    @Test
+    public void testDeviceEffects_darkThemeFromUser_appliedImmediately() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        when(mPowerManager.isInteractive()).thenReturn(true);
+
+        mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
+                UPDATE_ORIGIN_USER);
+
+        // Effect was applied, and no broadcast receiver was registered.
+        verify(mUiModeManager).setNightModeActivatedForCustomMode(
+                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+        verify(mContext, never()).registerReceiver(any(), any(), anyInt());
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ee08fd2..8bb11a4 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -220,6 +220,7 @@
 import android.service.notification.NotificationRankingUpdate;
 import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
@@ -8929,8 +8930,8 @@
         mBinderService.addAutomaticZenRule(rule, "com.android.settings");
 
         // verify that zen mode helper gets passed in a package name of "android"
-        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString(),
-                anyInt(), eq(ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI)); // system call
+        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
+                eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
     }
 
     @Test
@@ -8951,8 +8952,8 @@
         mBinderService.addAutomaticZenRule(rule, "com.android.settings");
 
         // verify that zen mode helper gets passed in a package name of "android"
-        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString(),
-                anyInt(), eq(ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI));  // system call
+        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
+                eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
     }
 
     @Test
@@ -8972,8 +8973,8 @@
 
         // verify that zen mode helper gets passed in the package name from the arg, not the owner
         verify(mockZenModeHelper).addAutomaticZenRule(
-                eq("another.package"), eq(rule), anyString(), anyInt(),
-                eq(ZenModeHelper.FROM_APP));  // doesn't count as a system/systemui call
+                eq("another.package"), eq(rule), eq(ZenModeConfig.UPDATE_ORIGIN_APP),
+                anyString(), anyInt());  // doesn't count as a system/systemui call
     }
 
     @Test
@@ -13185,7 +13186,7 @@
         NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
         mBinderService.setNotificationPolicy("package", policy);
 
-        verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean());
+        verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
     }
 
     @Test
@@ -13207,7 +13208,7 @@
         NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
         mBinderService.setNotificationPolicy("package", policy);
 
-        verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean());
+        verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
     }
 
     @Test
@@ -13223,7 +13224,7 @@
         NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
         mBinderService.setNotificationPolicy("package", policy);
 
-        verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean());
+        verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
     }
 
     @Test
@@ -13271,7 +13272,8 @@
         mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
 
         verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
-                eq("package"), anyString(), anyInt(), anyBoolean());
+                eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), eq("package"),
+                anyInt());
     }
 
     @Test
@@ -13280,6 +13282,7 @@
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
         mService.mZenModeHelper = zenModeHelper;
+        mService.setCallerIsNormalPackage();
         when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
                 .thenReturn(true);
         when(mCompanionMgr.getAssociations(anyString(), anyInt()))
@@ -13292,7 +13295,7 @@
         mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
 
         verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
-                eq("package"), anyString(), anyInt(), anyBoolean());
+                eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index fe21103..5ddac03 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -67,7 +67,6 @@
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -2370,7 +2369,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
                 uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         // create notification channel that can bypass dnd
@@ -2380,18 +2379,18 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
                 uid, false);
         assertTrue(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         // delete channels
         mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false);
         assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2407,7 +2406,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
                 uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         // Recreate a channel & now the app has dnd access granted and can set the bypass dnd field
@@ -2417,7 +2416,7 @@
                 uid, false);
 
         assertTrue(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2433,7 +2432,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
                 uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         // create notification channel that can bypass dnd, using local app level settings
@@ -2443,18 +2442,18 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
                 uid, false);
         assertTrue(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         // delete channels
         mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false);
         assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2481,8 +2480,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
                 uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(),
-                anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2504,7 +2502,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
                 uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2526,7 +2524,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
                 uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2542,7 +2540,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
                 uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         // update channel so it CAN bypass dnd:
@@ -2550,7 +2548,7 @@
         channel.setBypassDnd(true);
         mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true);
         assertTrue(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         // update channel so it can't bypass dnd:
@@ -2558,7 +2556,7 @@
         channel.setBypassDnd(false);
         mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2571,7 +2569,7 @@
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper.syncChannelsBypassingDnd();
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2581,7 +2579,7 @@
         mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index ef6fced..646ee3f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -45,6 +45,12 @@
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 import static android.service.notification.Condition.STATE_FALSE;
 import static android.service.notification.Condition.STATE_TRUE;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
 
@@ -53,9 +59,6 @@
 import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
 import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
 import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
-import static com.android.server.notification.ZenModeHelper.FROM_APP;
-import static com.android.server.notification.ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI;
-import static com.android.server.notification.ZenModeHelper.FROM_USER;
 import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -305,8 +308,8 @@
         mZenModeHelper.writeXml(serializer, false, version, UserHandle.USER_ALL);
         serializer.endDocument();
         serializer.flush();
-        mZenModeHelper.setConfig(new ZenModeConfig(), null, "writing xml", Process.SYSTEM_UID,
-                true);
+        mZenModeHelper.setConfig(new ZenModeConfig(), null, UPDATE_ORIGIN_INIT, "writing xml",
+                Process.SYSTEM_UID);
         return baos;
     }
 
@@ -321,7 +324,8 @@
         serializer.flush();
         ZenModeConfig newConfig = new ZenModeConfig();
         newConfig.user = userId;
-        mZenModeHelper.setConfig(newConfig, null, "writing xml", Process.SYSTEM_UID, true);
+        mZenModeHelper.setConfig(newConfig, null, UPDATE_ORIGIN_INIT, "writing xml",
+                Process.SYSTEM_UID);
         return baos;
     }
 
@@ -838,7 +842,7 @@
         mZenModeHelper.mConfig = null; // will evaluate config to zen mode off
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
-            mZenModeHelper.evaluateZenModeLocked("test", true);
+            mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -865,7 +869,7 @@
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
-            mZenModeHelper.evaluateZenModeLocked("test", true);
+            mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -892,7 +896,7 @@
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
-            mZenModeHelper.evaluateZenModeLocked("test", true);
+            mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -905,8 +909,8 @@
         setupZenConfig();
 
         // Turn manual zen mode on
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null,
-                "test", CUSTOM_PKG_UID, false);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+                null, "test", CUSTOM_PKG_UID);
 
         // audio manager shouldn't do anything until the handler processes its messages
         verify(mAudioManager, never()).updateRingerModeAffectedStreamsInternal();
@@ -1185,7 +1189,8 @@
         List<StatsEvent> events = new LinkedList<>();
 
         mZenModeHelper.pullRules(events);
-        mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, "test", CUSTOM_PKG_UID, false);
+        mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, UPDATE_ORIGIN_APP, "test",
+                CUSTOM_PKG_UID);
         assertTrue(-1
                 == mZenModeHelper.mRulesUidCache.getOrDefault(CUSTOM_PKG_NAME + "|" + 0, -1));
     }
@@ -1240,12 +1245,14 @@
         config10.user = 10;
         config10.allowAlarms = true;
         config10.allowMedia = true;
-        mZenModeHelper.setConfig(config10, null, "writeXml", Process.SYSTEM_UID, true);
+        mZenModeHelper.setConfig(config10, null, UPDATE_ORIGIN_INIT, "writeXml",
+                Process.SYSTEM_UID);
         ZenModeConfig config11 = mZenModeHelper.mConfig.copy();
         config11.user = 11;
         config11.allowAlarms = false;
         config11.allowMedia = false;
-        mZenModeHelper.setConfig(config11, null, "writeXml", Process.SYSTEM_UID, true);
+        mZenModeHelper.setConfig(config11, null, UPDATE_ORIGIN_INIT, "writeXml",
+                Process.SYSTEM_UID);
 
         // Backup user 10 and reset values.
         ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, 10);
@@ -1849,8 +1856,8 @@
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
             // We need the package name to be something that's not "android" so there aren't any
             // existing rules under that package.
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, FROM_APP);
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+                    "test", CUSTOM_PKG_UID);
             assertNotNull(id);
         }
         try {
@@ -1860,8 +1867,8 @@
                     ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, FROM_APP);
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+                    "test", CUSTOM_PKG_UID);
             fail("allowed too many rules to be created");
         } catch (IllegalArgumentException e) {
             // yay
@@ -1881,8 +1888,8 @@
                     ZenModeConfig.toScheduleConditionId(si),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, FROM_APP);
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+                    "test", CUSTOM_PKG_UID);
             assertNotNull(id);
         }
         try {
@@ -1892,8 +1899,8 @@
                     ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, FROM_APP);
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+                    "test", CUSTOM_PKG_UID);
             fail("allowed too many rules to be created");
         } catch (IllegalArgumentException e) {
             // yay
@@ -1913,8 +1920,8 @@
                     ZenModeConfig.toScheduleConditionId(si),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, FROM_APP);
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+                    "test", CUSTOM_PKG_UID);
             assertNotNull(id);
         }
         try {
@@ -1924,8 +1931,8 @@
                     ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, FROM_APP);
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+                    "test", CUSTOM_PKG_UID);
             fail("allowed too many rules to be created");
         } catch (IllegalArgumentException e) {
             // yay
@@ -1940,8 +1947,8 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         assertTrue(id != null);
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -1961,8 +1968,8 @@
                 new ComponentName("android", "ScheduleConditionProvider"),
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         assertTrue(id != null);
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -1985,11 +1992,12 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
-        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
-                CUSTOM_PKG_UID, FROM_APP);
+        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+                CUSTOM_PKG_UID);
         mZenModeHelper.setAutomaticZenRuleState(zenRule.getConditionId(),
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                CUSTOM_PKG_UID, false);
+                UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
 
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
         assertEquals(STATE_TRUE, ruleInConfig.condition.state);
@@ -2004,8 +2012,8 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
-        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
-                CUSTOM_PKG_UID, FROM_APP);
+        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+                CUSTOM_PKG_UID);
 
         AutomaticZenRule zenRule2 = new AutomaticZenRule("NEW",
                 null,
@@ -2014,7 +2022,7 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
-        mZenModeHelper.updateAutomaticZenRule(id, zenRule2, "", CUSTOM_PKG_UID, FROM_APP);
+        mZenModeHelper.updateAutomaticZenRule(id, zenRule2, UPDATE_ORIGIN_APP, "", CUSTOM_PKG_UID);
 
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
         assertEquals("NEW", ruleInConfig.name);
@@ -2029,15 +2037,15 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
-        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
-                CUSTOM_PKG_UID, FROM_APP);
+        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+                CUSTOM_PKG_UID);
 
         assertTrue(id != null);
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
         assertTrue(ruleInConfig != null);
         assertEquals(zenRule.getName(), ruleInConfig.name);
 
-        mZenModeHelper.removeAutomaticZenRule(id, "test", CUSTOM_PKG_UID, false);
+        mZenModeHelper.removeAutomaticZenRule(id, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
         assertNull(mZenModeHelper.mConfig.automaticRules.get(id));
     }
 
@@ -2049,16 +2057,16 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
-                CUSTOM_PKG_UID, FROM_APP);
+        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+                CUSTOM_PKG_UID);
 
         assertTrue(id != null);
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
         assertTrue(ruleInConfig != null);
         assertEquals(zenRule.getName(), ruleInConfig.name);
 
-        mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), "test",
-                CUSTOM_PKG_UID, false);
+        mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), UPDATE_ORIGIN_APP, "test",
+                CUSTOM_PKG_UID);
         assertNull(mZenModeHelper.mConfig.automaticRules.get(id));
     }
 
@@ -2073,17 +2081,18 @@
                 new ComponentName("android", "ScheduleConditionProvider"),
                 sharedUri,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
         AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
                 new ComponentName("android", "ScheduleConditionProvider"),
                 sharedUri,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         Condition condition = new Condition(sharedUri, "", STATE_TRUE);
-        mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, Process.SYSTEM_UID, true);
+        mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
             if (rule.id.equals(id)) {
@@ -2097,7 +2106,8 @@
         }
 
         condition = new Condition(sharedUri, "", STATE_FALSE);
-        mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, Process.SYSTEM_UID, true);
+        mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
             if (rule.id.equals(id)) {
@@ -2133,7 +2143,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(zde)
                         .build(),
-                "reasons", 0, FROM_APP);
+                UPDATE_ORIGIN_APP, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(
@@ -2167,7 +2177,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(zde)
                         .build(),
-                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
@@ -2195,7 +2205,8 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(zde)
                         .build(),
-                "reasons", 0, FROM_USER);
+                UPDATE_ORIGIN_USER,
+                "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
@@ -2212,7 +2223,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(original)
                         .build(),
-                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
 
         ZenDeviceEffects updateFromApp = new ZenDeviceEffects.Builder()
                 .setShouldUseNightMode(true) // Good
@@ -2223,7 +2234,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(updateFromApp)
                         .build(),
-                "reasons", 0, FROM_APP);
+                UPDATE_ORIGIN_APP, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(
@@ -2244,7 +2255,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(original)
                         .build(),
-                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
 
         ZenDeviceEffects updateFromSystem = new ZenDeviceEffects.Builder()
                 .setShouldUseNightMode(true) // Good
@@ -2254,7 +2265,7 @@
                 new AutomaticZenRule.Builder("Rule", CONDITION_ID)
                         .setDeviceEffects(updateFromSystem)
                         .build(),
-                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromSystem);
@@ -2271,7 +2282,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(original)
                         .build(),
-                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
 
         ZenDeviceEffects updateFromUser = new ZenDeviceEffects.Builder()
                 .setShouldUseNightMode(true) // Good
@@ -2281,7 +2292,7 @@
                 new AutomaticZenRule.Builder("Rule", CONDITION_ID)
                         .setDeviceEffects(updateFromUser)
                         .build(),
-                "reasons", 0, FROM_USER);
+                UPDATE_ORIGIN_USER, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser);
@@ -2293,16 +2304,16 @@
         setupZenConfig();
 
         // note that caller=null because that's how it comes in from NMS.setZenMode
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
 
         // confirm that setting zen mode via setManualZenMode changed the zen mode correctly
         assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
         assertEquals(true, mZenModeHelper.mConfig.manualRule.allowManualInvocation);
 
         // and also that it works to turn it back off again
-        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                "", null, Process.SYSTEM_UID);
 
         assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode);
     }
@@ -2312,15 +2323,15 @@
         setupZenConfig();
 
         // note that caller=null because that's how it comes in from NMS.setZenMode
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
 
         // confirm that setting zen mode via setManualZenMode changed the zen mode correctly
         assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
 
         // and also that it works to turn it back off again
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+                null, Process.SYSTEM_UID);
 
         assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
     }
@@ -2332,13 +2343,13 @@
 
         // Turn zen mode on (to important_interruptions)
         // Need to additionally call the looper in order to finish the post-apply-config process
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
 
         // Now turn zen mode off, but via a different package UID -- this should get registered as
         // "not an action by the user" because some other app is changing zen mode
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "", CUSTOM_PKG_UID,
-                false);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "", null,
+                CUSTOM_PKG_UID);
 
         // In total, this should be 2 loggable changes
         assertEquals(2, mZenModeEventLogger.numLoggedChanges());
@@ -2397,17 +2408,18 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                Process.SYSTEM_UID);
 
         // Event 2: "User" turns off the automatic rule (sets it to not enabled)
         zenRule.setEnabled(false);
-        mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID,
-                FROM_SYSTEM_OR_SYSTEMUI);
+        mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+                Process.SYSTEM_UID);
 
         // Add a new system rule
         AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
@@ -2417,15 +2429,16 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule,
-                "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // Event 3: turn on the system rule
         mZenModeHelper.setAutomaticZenRuleState(systemId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Event 4: "User" deletes the rule
-        mZenModeHelper.removeAutomaticZenRule(systemId, "", Process.SYSTEM_UID, true);
+        mZenModeHelper.removeAutomaticZenRule(systemId, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+                Process.SYSTEM_UID);
 
         // In total, this represents 4 events
         assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -2486,26 +2499,26 @@
         setupZenConfig();
 
         // First just turn zen mode on
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
 
         // Now change the policy slightly; want to confirm that this'll be reflected in the logs
         ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
         newConfig.allowAlarms = true;
         newConfig.allowRepeatCallers = false;
-        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
-                true);
+        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Turn zen mode off; we want to make sure policy changes do not get logged when zen mode
         // is off.
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+                null, Process.SYSTEM_UID);
 
         // Change the policy again
         newConfig.allowMessages = false;
         newConfig.allowRepeatCallers = true;
-        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
-                true);
+        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Total events: we only expect ones for turning on, changing policy, and turning off
         assertEquals(3, mZenModeEventLogger.numLoggedChanges());
@@ -2548,8 +2561,8 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // Rule 2, same as rule 1
         AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -2558,8 +2571,8 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // Rule 3, has stricter settings than the default settings
         ZenModeConfig ruleConfig = mZenModeHelper.mConfig.copy();
@@ -2572,28 +2585,28 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 ruleConfig.toZenPolicy(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // First: turn on rule 1
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,  Process.SYSTEM_UID);
 
         // Second: turn on rule 2
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,  Process.SYSTEM_UID);
 
         // Third: turn on rule 3
         mZenModeHelper.setAutomaticZenRuleState(id3,
                 new Condition(zenRule3.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,  Process.SYSTEM_UID);
 
         // Fourth: Turn *off* rule 2
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,  Process.SYSTEM_UID);
 
         // This should result in a total of four events
         assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -2649,7 +2662,7 @@
         // Artificially turn zen mode "on". Re-evaluating zen mode should cause it to turn back off
         // given that we don't have any zen rules active.
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        mZenModeHelper.evaluateZenModeLocked("test", true);
+        mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
 
         // Check that the change actually took: zen mode should be off now
         assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
@@ -2672,8 +2685,8 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // Rule 2, same as rule 1 but owned by the system
         AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -2682,37 +2695,37 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // Turn on rule 1; call looks like it's from the system. Because setting a condition is
         // typically an automatic (non-user-initiated) action, expect the calling UID to be
         // re-evaluated to the one associat.d with CUSTOM_PKG_NAME.
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Second: turn on rule 2. This is a system-owned rule and the UID should not be modified
         // (nor even looked up; the mock PackageManager won't handle "android" as input).
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Disable rule 1. Because this looks like a user action, the UID should not be modified
         // from the system-provided one.
         zenRule.setEnabled(false);
-        mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID,
-                FROM_SYSTEM_OR_SYSTEMUI);
+        mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+                Process.SYSTEM_UID);
 
         // Add a manual rule. Any manual rule changes should not get calling uids reassigned.
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
-                CUSTOM_PKG_UID, false);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+                "", null, CUSTOM_PKG_UID);
 
         // Change rule 2's condition, but from some other UID. Since it doesn't look like it's from
         // the system, we keep the UID info.
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
-                12345, false);
+                UPDATE_ORIGIN_APP, 12345);
 
         // That was 5 events total
         assertEquals(5, mZenModeEventLogger.numLoggedChanges());
@@ -2767,26 +2780,26 @@
         // Turn on zen mode with a manual rule with an enabler set. This should *not* count
         // as a user action, and *should* get its UID reassigned.
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                CUSTOM_PKG_NAME, "", Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", CUSTOM_PKG_NAME, Process.SYSTEM_UID);
 
         // Now change apps bypassing to true
         ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
         newConfig.areChannelsBypassingDnd = true;
-        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
-                true);
+        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // and then back to false, all without changing anything else
         newConfig.areChannelsBypassingDnd = false;
-        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
-                true);
+        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Turn off manual mode, call from a package: don't reset UID even though enabler is set
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null,
-                CUSTOM_PKG_NAME, "", 12345, false);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "",
+                CUSTOM_PKG_NAME, 12345);
 
         // And likewise when turning it back on again
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                CUSTOM_PKG_NAME, "", 12345, false);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+                "", CUSTOM_PKG_NAME, 12345);
 
         // These are 5 events in total.
         assertEquals(5, mZenModeEventLogger.numLoggedChanges());
@@ -2831,15 +2844,15 @@
         setupZenConfig();
 
         // First just turn zen mode on
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
 
         // Now change only the channels part of the policy; want to confirm that this'll be
         // reflected in the logs
         ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
         newConfig.allowPriorityChannels = false;
-        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
-                true);
+        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Total events: one for turning on, one for changing policy
         assertThat(mZenModeEventLogger.numLoggedChanges()).isEqualTo(2);
@@ -2881,13 +2894,13 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // enable the rule
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // inspect the consolidated policy. Based on setupZenConfig() values.
         assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms());
@@ -2924,13 +2937,13 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 customPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // enable the rule; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // since this is the only active rule, the consolidated policy should match the custom
         // policy for every field specified, and take default values for unspecified things
@@ -2960,13 +2973,13 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // enable rule 1
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // custom policy for rule 2
         ZenPolicy customPolicy = new ZenPolicy.Builder()
@@ -2985,12 +2998,12 @@
                 customPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
-                "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // enable rule 2; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // now both rules should be on, and the consolidated policy should reflect the most
         // restrictive option of each of the two
@@ -3022,13 +3035,13 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 customPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // enable the rule; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // confirm that channels make it through
         assertTrue(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
@@ -3045,12 +3058,12 @@
                 strictPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
-                "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // enable rule 2; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // rule 2 should override rule 1
         assertFalse(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
@@ -3121,7 +3134,7 @@
 
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
 
-        mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, true, FROM_APP);
+        mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, UPDATE_ORIGIN_APP, true);
 
         assertEquals(NAME, rule.name);
         assertEquals(OWNER, rule.component);
@@ -3149,7 +3162,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[1];
@@ -3165,8 +3178,8 @@
         mZenModeHelper.addCallback(callback);
 
         zenRule.setEnabled(false);
-        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID,
-                FROM_SYSTEM_OR_SYSTEMUI);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                "", Process.SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         assertEquals(AUTOMATIC_RULE_STATUS_DISABLED, actualStatus[0]);
@@ -3184,7 +3197,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, false);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[1];
@@ -3200,8 +3213,8 @@
         mZenModeHelper.addCallback(callback);
 
         zenRule.setEnabled(true);
-        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID,
-                FROM_SYSTEM_OR_SYSTEMUI);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                "", Process.SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         assertEquals(AUTOMATIC_RULE_STATUS_ENABLED, actualStatus[0]);
@@ -3220,7 +3233,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[1];
@@ -3237,7 +3250,7 @@
 
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         assertEquals(AUTOMATIC_RULE_STATUS_ACTIVATED, actualStatus[0]);
@@ -3256,7 +3269,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[2];
@@ -3274,10 +3287,10 @@
 
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
-        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                null, "", Process.SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]);
@@ -3296,7 +3309,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[2];
@@ -3314,11 +3327,11 @@
 
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_FALSE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]);
@@ -3336,21 +3349,21 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Event 2: Snooze rule by turning off DND
-        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                "", null, Process.SYSTEM_UID);
 
         // Event 3: "User" turns off the automatic rule (sets it to not enabled)
         zenRule.setEnabled(false);
-        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID,
-                FROM_SYSTEM_OR_SYSTEMUI);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                "", Process.SYSTEM_UID);
 
         assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing);
     }
@@ -3359,18 +3372,20 @@
     public void testDeviceEffects_applied() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
 
         ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
                 .setShouldSuppressAmbientDisplay(true)
                 .setShouldDimWallpaper(true)
                 .build();
         String ruleId = addRuleWithEffects(effects);
-        verify(mDeviceEffectsApplier, never()).apply(any());
+        verifyNoMoreInteractions(mDeviceEffectsApplier);
 
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
 
-        verify(mDeviceEffectsApplier).apply(eq(effects));
+        verify(mDeviceEffectsApplier).apply(eq(effects), eq(UPDATE_ORIGIN_APP));
     }
 
     @Test
@@ -3380,31 +3395,66 @@
 
         ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
         String ruleId = addRuleWithEffects(zde);
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
-        verify(mDeviceEffectsApplier).apply(eq(zde));
+        verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_APP));
 
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, CUSTOM_PKG_UID, false);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
 
-        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS));
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_APP));
     }
 
     @Test
+    public void testDeviceEffects_changeToConsolidatedEffects_applied() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
+
+        String ruleId = addRuleWithEffects(
+                new ZenDeviceEffects.Builder().setShouldDisplayGrayscale(true).build());
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
+        mTestableLooper.processAllMessages();
+        verify(mDeviceEffectsApplier).apply(
+                eq(new ZenDeviceEffects.Builder()
+                        .setShouldDisplayGrayscale(true)
+                        .build()),
+                eq(UPDATE_ORIGIN_APP));
+
+        // Now create and activate a second rule that adds more effects.
+        String secondRuleId = addRuleWithEffects(
+                new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build());
+        mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
+        mTestableLooper.processAllMessages();
+
+        verify(mDeviceEffectsApplier).apply(
+                eq(new ZenDeviceEffects.Builder()
+                        .setShouldDisplayGrayscale(true)
+                        .setShouldDimWallpaper(true)
+                        .build()),
+                eq(UPDATE_ORIGIN_APP));
+    }
+    @Test
     public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
 
         ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
         String ruleId = addRuleWithEffects(zde);
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
-        verify(mDeviceEffectsApplier).apply(eq(zde));
+        verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_APP));
 
         // Now create and activate a second rule that doesn't add any more effects.
         String secondRuleId = addRuleWithEffects(zde);
-        mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, CUSTOM_PKG_UID,
-                false);
+        mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
 
         verifyNoMoreInteractions(mDeviceEffectsApplier);
@@ -3416,21 +3466,50 @@
 
         ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
         String ruleId = addRuleWithEffects(zde);
-        verify(mDeviceEffectsApplier, never()).apply(any());
+        verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
 
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
-        verify(mDeviceEffectsApplier, never()).apply(any());
+        verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
 
         mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
-        verify(mDeviceEffectsApplier).apply(eq(zde));
+        verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_INIT));
+    }
+
+    @Test
+    public void testDeviceEffects_onUserSwitch_appliedImmediately() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
+
+        // Initialize default configurations (default rules) for both users.
+        mZenModeHelper.onUserSwitched(1);
+        mZenModeHelper.onUserSwitched(2);
+
+        // Current user is now 2. Tweak a rule for user 1 so it's active and has effects.
+        ZenRule user1Rule = mZenModeHelper.mConfigs.get(1).automaticRules.valueAt(0);
+        user1Rule.enabled = true;
+        user1Rule.condition = new Condition(user1Rule.conditionId, "on", STATE_TRUE);
+        user1Rule.zenDeviceEffects = new ZenDeviceEffects.Builder()
+                .setShouldDimWallpaper(true)
+                .setShouldUseNightMode(true)
+                .build();
+        verifyNoMoreInteractions(mDeviceEffectsApplier);
+
+        mZenModeHelper.onUserSwitched(1);
+        mTestableLooper.processAllMessages();
+
+        verify(mDeviceEffectsApplier).apply(eq(user1Rule.zenDeviceEffects),
+                eq(UPDATE_ORIGIN_INIT_USER));
     }
 
     private String addRuleWithEffects(ZenDeviceEffects effects) {
         AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
                 .setDeviceEffects(effects)
                 .build();
-        return mZenModeHelper.addAutomaticZenRule("pkg", rule, "", CUSTOM_PKG_UID, FROM_APP);
+        return mZenModeHelper.addAutomaticZenRule("pkg", rule, UPDATE_ORIGIN_APP, "",
+                CUSTOM_PKG_UID);
     }
 
     @Test
@@ -3455,7 +3534,7 @@
 
         mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
                 ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, "test", "test", 0, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "test", "test", 0);
         assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
 
         mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
@@ -3505,7 +3584,7 @@
         assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
         assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse();
 
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, "test", "test", 0, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "test", "test", 0);
         assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isTrue();
 
         mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index c3074bb..ef19791 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -99,11 +99,6 @@
             android:theme="@style/WhiteBackgroundTheme"
             android:exported="true"/>
 
-        <activity android:name="com.android.server.wm.TrustedPresentationCallbackTest$TestActivity"
-            android:exported="true"
-            android:showWhenLocked="true"
-            android:turnScreenOn="true" />
-
         <activity android:name="android.app.Activity"
             android:exported="true"
             android:showWhenLocked="true"
diff --git a/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
new file mode 100644
index 0000000..44b69f1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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.hardware.display.DeviceProductInfo.CONNECTION_TO_SINK_UNKNOWN;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorners.NO_ROUNDED_CORNERS;
+
+import static com.android.server.wm.DeferredDisplayUpdater.DEFERRABLE_FIELDS;
+import static com.android.server.wm.DeferredDisplayUpdater.DIFF_NOT_WM_DEFERRABLE;
+import static com.android.server.wm.DeferredDisplayUpdater.DIFF_WM_DEFERRABLE;
+import static com.android.server.wm.DeferredDisplayUpdater.calculateDisplayInfoDiff;
+import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.NonNull;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.hardware.display.DeviceProductInfo;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.DisplayCutout;
+import android.view.DisplayInfo;
+import android.view.DisplayShape;
+import android.view.RoundedCorner;
+import android.view.RoundedCorners;
+import android.view.SurfaceControl.RefreshRateRange;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:DeferredDisplayUpdaterDiffTest
+ */
+@SmallTest
+@Presubmit
+public class DeferredDisplayUpdaterDiffTest {
+
+    private static final Set<String> IGNORED_FIELDS = new HashSet<>(Arrays.asList(
+            "name" // human-readable name is ignored in equals() checks
+    ));
+
+    private static final DisplayInfo EMPTY = new DisplayInfo();
+
+    @Test
+    public void testCalculateDisplayInfoDiff_allDifferent_returnsChanges() {
+        final DisplayInfo first = new DisplayInfo();
+        final DisplayInfo second = new DisplayInfo();
+        makeAllFieldsDifferent(first, second);
+
+        int diff = calculateDisplayInfoDiff(first, second);
+
+        assertWithMessage("Expected to receive a non-zero difference when "
+                + "there are changes in all fields of DisplayInfo\n"
+                + "Make sure that you have updated calculateDisplayInfoDiff function after "
+                + "changing DisplayInfo fields").that(diff).isGreaterThan(0);
+    }
+
+    @Test
+    public void testCalculateDisplayInfoDiff_forEveryDifferentField_returnsChanges() {
+        generateWithSingleDifferentField((first, second, field) -> {
+            int diff = calculateDisplayInfoDiff(first, second);
+
+            assertWithMessage("Expected to receive a non-zero difference when "
+                    + "there are changes in " + field + "\n"
+                    + "Make sure that you have updated calculateDisplayInfoDiff function after "
+                    + "changing DisplayInfo fields").that(diff).isGreaterThan(0);
+        });
+    }
+
+    @Test
+    public void testCalculateDisplayInfoDiff_forEveryDifferentField_returnsMatchingChange() {
+        generateWithSingleDifferentField((first, second, field) -> {
+            boolean hasDeferrableFieldChange = hasDeferrableFieldChange(first, second);
+            int expectedDiff =
+                    hasDeferrableFieldChange ? DIFF_WM_DEFERRABLE : DIFF_NOT_WM_DEFERRABLE;
+
+            int diff = calculateDisplayInfoDiff(first, second);
+
+            assertWithMessage("Expected to have diff = " + expectedDiff
+                    + ", for field = " + field + "\n"
+                    + "Make sure that you have updated calculateDisplayInfoDiff function after "
+                    + "changing DisplayInfo fields").that(
+                    diff).isEqualTo(expectedDiff);
+        });
+    }
+
+    /**
+     * Sets each field of the objects to different values using reflection
+     */
+    private static void makeAllFieldsDifferent(@NonNull DisplayInfo first,
+            @NonNull DisplayInfo second) {
+        forEachDisplayInfoField(field -> {
+            try {
+                setDifferentFieldValues(first, second, field);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
+    private static boolean hasDeferrableFieldChange(@NonNull DisplayInfo first,
+            @NonNull DisplayInfo second) {
+        final DisplayInfo firstDeferrableFieldsOnly = new DisplayInfo();
+        final DisplayInfo secondDeferrableFieldsOnly = new DisplayInfo();
+
+        copyDisplayInfoFields(/* out= */ firstDeferrableFieldsOnly, /* base= */
+                EMPTY, /* override= */ first, DEFERRABLE_FIELDS);
+        copyDisplayInfoFields(/* out= */ secondDeferrableFieldsOnly, /* base= */
+                EMPTY, /* override= */ second, DEFERRABLE_FIELDS);
+
+        return !firstDeferrableFieldsOnly.equals(secondDeferrableFieldsOnly);
+    }
+
+    /**
+     * Creates pairs of DisplayInfos where only one field is different, the callback is called for
+     * each field
+     */
+    private static void generateWithSingleDifferentField(DisplayInfoConsumer consumer) {
+        forEachDisplayInfoField(field -> {
+            final DisplayInfo first = new DisplayInfo();
+            final DisplayInfo second = new DisplayInfo();
+
+            try {
+                setDifferentFieldValues(first, second, field);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+
+            consumer.consume(first, second, field);
+        });
+    }
+
+    private static void setDifferentFieldValues(@NonNull DisplayInfo first,
+            @NonNull DisplayInfo second,
+            @NonNull Field field) throws IllegalAccessException {
+        final Class<?> type = field.getType();
+        if (type.equals(int.class)) {
+            field.setInt(first, 1);
+            field.setInt(second, 2);
+        } else if (type.equals(double.class)) {
+            field.setDouble(first, 1.0);
+            field.setDouble(second, 2.0);
+        } else if (type.equals(short.class)) {
+            field.setShort(first, (short) 1);
+            field.setShort(second, (short) 2);
+        } else if (type.equals(long.class)) {
+            field.setLong(first, 1L);
+            field.setLong(second, 2L);
+        } else if (type.equals(char.class)) {
+            field.setChar(first, 'a');
+            field.setChar(second, 'b');
+        } else if (type.equals(byte.class)) {
+            field.setByte(first, (byte) 1);
+            field.setByte(second, (byte) 2);
+        } else if (type.equals(float.class)) {
+            field.setFloat(first, 1.0f);
+            field.setFloat(second, 2.0f);
+        } else if (type == boolean.class) {
+            field.setBoolean(first, true);
+            field.setBoolean(second, false);
+        } else if (type.equals(String.class)) {
+            field.set(first, "one");
+            field.set(second, "two");
+        } else if (type.equals(DisplayAddress.class)) {
+            field.set(first, DisplayAddress.fromPhysicalDisplayId(0));
+            field.set(second, DisplayAddress.fromPhysicalDisplayId(1));
+        } else if (type.equals(DeviceProductInfo.class)) {
+            field.set(first, new DeviceProductInfo("name", "pnp_id", "product_id1", 2023,
+                    CONNECTION_TO_SINK_UNKNOWN));
+            field.set(second, new DeviceProductInfo("name", "pnp_id", "product_id2", 2023,
+                    CONNECTION_TO_SINK_UNKNOWN));
+        } else if (type.equals(DisplayCutout.class)) {
+            field.set(first,
+                    new DisplayCutout(Insets.NONE, new Rect(0, 0, 100, 100), null, null,
+                            null));
+            field.set(second,
+                    new DisplayCutout(Insets.NONE, new Rect(0, 0, 200, 200), null, null,
+                            null));
+        } else if (type.equals(RoundedCorners.class)) {
+            field.set(first, NO_ROUNDED_CORNERS);
+
+            final RoundedCorners other = new RoundedCorners(NO_ROUNDED_CORNERS);
+            other.setRoundedCorner(POSITION_TOP_LEFT,
+                    new RoundedCorner(POSITION_TOP_LEFT, 1, 2, 3));
+            field.set(second, other);
+        } else if (type.equals(DisplayShape.class)) {
+            field.set(first, DisplayShape.createDefaultDisplayShape(100, 200, false));
+            field.set(second, DisplayShape.createDefaultDisplayShape(50, 100, false));
+        } else if (type.equals(RefreshRateRange.class)) {
+            field.set(first, new RefreshRateRange(0, 100));
+            field.set(second, new RefreshRateRange(20, 80));
+        } else if (type.equals(Display.HdrCapabilities.class)) {
+            field.set(first, new Display.HdrCapabilities(new int[]{0}, 100, 50, 25));
+            field.set(second, new Display.HdrCapabilities(new int[]{1}, 100, 50, 25));
+        } else if (type.equals(SparseArray.class)
+                && ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0].equals(
+                RefreshRateRange.class)) {
+            final SparseArray<RefreshRateRange> array1 = new SparseArray<>();
+            array1.set(0, new RefreshRateRange(0, 100));
+            final SparseArray<RefreshRateRange> array2 = new SparseArray<>();
+            array2.set(0, new RefreshRateRange(20, 80));
+            field.set(first, array1);
+            field.set(second, array2);
+        } else if (type.isArray() && type.getComponentType().equals(int.class)) {
+            field.set(first, new int[]{0});
+            field.set(second, new int[]{1});
+        } else if (type.isArray() && type.getComponentType().equals(Display.Mode.class)) {
+            field.set(first, new Display.Mode[]{new Display.Mode(100, 200, 300)});
+            field.set(second, new Display.Mode[]{new Display.Mode(10, 20, 30)});
+        } else {
+            throw new IllegalArgumentException("Field " + field
+                    + " is not supported by this test, please add implementation of setting "
+                    + "different values for this field");
+        }
+    }
+
+    private interface DisplayInfoConsumer {
+        void consume(DisplayInfo first, DisplayInfo second, Field field);
+    }
+
+    /**
+     * Iterates over every non-static field of DisplayInfo class except IGNORED_FIELDS
+     */
+    private static void forEachDisplayInfoField(Consumer<Field> consumer) {
+        for (Field field : DisplayInfo.class.getDeclaredFields()) {
+            field.setAccessible(true);
+
+            if (Modifier.isStatic(field.getModifiers())) {
+                continue;
+            }
+
+            if (IGNORED_FIELDS.contains(field.getName())) {
+                continue;
+            }
+
+            consumer.accept(field);
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
new file mode 100644
index 0000000..dfa595c
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.TransitionController.OnStartCollect;
+import com.android.window.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Tests for the {@link DisplayContent} class when FLAG_DEFER_DISPLAY_UPDATES is enabled.
+ *
+ * Build/Install/Run:
+ * atest WmTests:DisplayContentDeferredUpdateTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
+
+    @Override
+    protected void onBeforeSystemServicesCreated() {
+        // Set other flags to their default values
+        mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
+
+        mSetFlagsRule.enableFlags(Flags.FLAG_DEFER_DISPLAY_UPDATES);
+    }
+
+    @Before
+    public void before() {
+        mockTransitionsController(/* enabled= */ true);
+        mockRemoteDisplayChangeController();
+    }
+
+    @Test
+    public void testUpdate_deferrableFieldChangedTransitionStarted_deferrableFieldUpdated() {
+        performInitialDisplayUpdate();
+
+        givenDisplayInfo(/* uniqueId= */ "old");
+        Runnable onUpdated = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+
+        // Emulate that collection has started
+        captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+        verify(onUpdated).run();
+        clearInvocations(mDisplayContent.mTransitionController, onUpdated);
+
+        givenDisplayInfo(/* uniqueId= */ "new");
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+        captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+        verify(onUpdated).run();
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new");
+    }
+
+    @Test
+    public void testUpdate_nonDeferrableUpdateAndTransitionDeferred_nonDeferrableFieldUpdated() {
+        performInitialDisplayUpdate();
+
+        // Update only color mode (non-deferrable field) and keep the same unique id
+        givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123);
+        Runnable onUpdated = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+
+        verify(onUpdated).run();
+        assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
+    }
+
+    @Test
+    public void testUpdate_nonDeferrableUpdateTwiceAndTransitionDeferred_fieldHasLatestValue() {
+        performInitialDisplayUpdate();
+
+        // Update only color mode (non-deferrable field) and keep the same unique id
+        givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123);
+        mDisplayContent.requestDisplayUpdate(mock(Runnable.class));
+
+        assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId)
+                .isEqualTo("initial_unique_id");
+
+        // Update unique id (deferrable field), keep the same color mode,
+        // this update should be deferred
+        givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 123);
+        mDisplayContent.requestDisplayUpdate(mock(Runnable.class));
+
+        assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId)
+                .isEqualTo("initial_unique_id");
+
+        // Update color mode again and keep the same unique id, color mode update
+        // should not be deferred, unique id update is still deferred as transition
+        // has not started collecting yet
+        givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 456);
+        Runnable onUpdated = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+
+        assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(456);
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId)
+                .isEqualTo("initial_unique_id");
+
+        // Mark transition as started collected, so pending changes are applied
+        captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+
+        // Verify that all fields have the latest values
+        verify(onUpdated).run();
+        assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(456);
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new_unique_id");
+    }
+
+    @Test
+    public void testUpdate_deferrableFieldUpdatedTransitionPending_fieldNotUpdated() {
+        performInitialDisplayUpdate();
+        givenDisplayInfo(/* uniqueId= */ "old");
+        Runnable onUpdated = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+        captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+        verify(onUpdated).run();
+        clearInvocations(mDisplayContent.mTransitionController, onUpdated);
+
+        givenDisplayInfo(/* uniqueId= */ "new");
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+
+        captureStartTransitionCollection(); // do not continue by not starting the collection
+        verify(onUpdated, never()).run();
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("old");
+    }
+
+    @Test
+    public void testTwoDisplayUpdates_transitionStarted_displayUpdated() {
+        performInitialDisplayUpdate();
+        givenDisplayInfo(/* uniqueId= */ "old");
+        Runnable onUpdated = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+        captureStartTransitionCollection().getValue()
+                .onCollectStarted(/* deferred= */ true);
+        verify(onUpdated).run();
+        clearInvocations(mDisplayContent.mTransitionController, onUpdated);
+
+        // Perform two display updates while WM is 'busy'
+        givenDisplayInfo(/* uniqueId= */ "new1");
+        Runnable onUpdated1 = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated1);
+        givenDisplayInfo(/* uniqueId= */ "new2");
+        Runnable onUpdated2 = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated2);
+
+        // Continue with the first update
+        captureStartTransitionCollection().getAllValues().get(0)
+                .onCollectStarted(/* deferred= */ true);
+        verify(onUpdated1).run();
+        verify(onUpdated2, never()).run();
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new1");
+
+        // Continue with the second update
+        captureStartTransitionCollection().getAllValues().get(1)
+                .onCollectStarted(/* deferred= */ true);
+        verify(onUpdated2).run();
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new2");
+    }
+
+    private void mockTransitionsController(boolean enabled) {
+        spyOn(mDisplayContent.mTransitionController);
+        when(mDisplayContent.mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled);
+        doReturn(true).when(mDisplayContent.mTransitionController).startCollectOrQueue(any(),
+                any());
+    }
+
+    private void mockRemoteDisplayChangeController() {
+        spyOn(mDisplayContent.mRemoteDisplayChangeController);
+        doReturn(true).when(mDisplayContent.mRemoteDisplayChangeController)
+                .performRemoteDisplayChange(anyInt(), anyInt(), any(), any());
+    }
+
+    private ArgumentCaptor<OnStartCollect> captureStartTransitionCollection() {
+        ArgumentCaptor<OnStartCollect> callbackCaptor =
+                ArgumentCaptor.forClass(OnStartCollect.class);
+        verify(mDisplayContent.mTransitionController, atLeast(1)).startCollectOrQueue(any(),
+                callbackCaptor.capture());
+        return callbackCaptor;
+    }
+
+    private void givenDisplayInfo(String uniqueId) {
+        givenDisplayInfo(uniqueId, /* colorMode= */ 0);
+    }
+
+    private void givenDisplayInfo(String uniqueId, int colorMode) {
+        spyOn(mDisplayContent.mDisplay);
+        doAnswer(invocation -> {
+            DisplayInfo info = invocation.getArgument(0);
+            info.uniqueId = uniqueId;
+            info.colorMode = colorMode;
+            return null;
+        }).when(mDisplayContent.mDisplay).getDisplayInfo(any());
+    }
+
+    private void performInitialDisplayUpdate() {
+        givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 0);
+        Runnable onUpdated = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index c0a90b2..e77c14a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -19,10 +19,13 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -62,9 +65,11 @@
 
     private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
     private static final FrameRateVote FRAME_RATE_VOTE_60_EXACT =
-            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_60_PREFERRED =
-            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
 
     WindowState createWindow(String name) {
         WindowState window = createWindow(null, TYPE_APPLICATION, name);
@@ -110,6 +115,8 @@
                 any(SurfaceControl.class), anyInt());
         verify(appWindow.getPendingTransaction(), never()).setFrameRate(
                 any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                any(SurfaceControl.class), anyInt());
     }
 
     @Test
@@ -140,6 +147,8 @@
                 appWindow.getSurfaceControl(), 1);
         verify(appWindow.getPendingTransaction(), never()).setFrameRate(
                 any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                any(SurfaceControl.class), anyInt());
     }
 
     @Test
@@ -175,8 +184,17 @@
                 appWindow.getSurfaceControl(), 0);
         verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), 1);
-        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
-                any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
+                eq(appWindow.getSurfaceControl()), anyFloat(),
+                eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
+        if (explicitRefreshRateHints()) {
+            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                    appWindow.getSurfaceControl(),
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+        } else {
+            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                    any(SurfaceControl.class), anyInt());
+        }
     }
 
     @Test
@@ -202,8 +220,17 @@
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), 2);
-        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
-                any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
+                eq(appWindow.getSurfaceControl()), anyFloat(),
+                eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
+        if (explicitRefreshRateHints()) {
+            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                    appWindow.getSurfaceControl(),
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+        } else {
+            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                    any(SurfaceControl.class), anyInt());
+        }
     }
 
     @Test
@@ -229,6 +256,8 @@
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         verify(appWindow.getPendingTransaction(), never()).setFrameRate(
                 any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                any(SurfaceControl.class), anyInt());
     }
 
     @Test
@@ -256,6 +285,14 @@
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 appWindow.getSurfaceControl(), 60,
                 Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
+        if (explicitRefreshRateHints()) {
+            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                    appWindow.getSurfaceControl(),
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+        } else {
+            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                    any(SurfaceControl.class), anyInt());
+        }
     }
 
     @Test
@@ -283,6 +320,8 @@
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         verify(appWindow.getPendingTransaction(), never()).setFrameRate(
                 any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                any(SurfaceControl.class), anyInt());
     }
 
     @Test
@@ -310,5 +349,13 @@
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 appWindow.getSurfaceControl(), 60,
                 Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS);
+        if (explicitRefreshRateHints()) {
+            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                    appWindow.getSurfaceControl(),
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+        } else {
+            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                    any(SurfaceControl.class), anyInt());
+        }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index ffa1ed9..38a66a9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -19,10 +19,13 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
@@ -432,6 +435,56 @@
 
     }
 
+    @SetupWindows(addWindows = W_INPUT_METHOD)
+    @Test
+    public void testConsumeImeInsets() {
+        final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+        final InsetsSource imeSource = new InsetsSource(ID_IME, ime());
+        imeSource.setVisible(true);
+        mImeWindow.mHasSurface = true;
+
+        final WindowState win1 = addWindow(TYPE_APPLICATION, "win1");
+        final WindowState win2 = addWindow(TYPE_APPLICATION, "win2");
+
+        win1.mAboveInsetsState.addSource(imeSource);
+        win1.mHasSurface = true;
+        win2.mAboveInsetsState.addSource(imeSource);
+        win2.mHasSurface = true;
+
+        assertTrue(mImeWindow.isVisible());
+        assertTrue(win1.isVisible());
+        assertTrue(win2.isVisible());
+
+        // Make sure both windows have visible IME insets.
+        assertTrue(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+
+        win2.mAttrs.privateFlags |= PRIVATE_FLAG_CONSUME_IME_INSETS;
+
+        displayPolicy.beginPostLayoutPolicyLw();
+        displayPolicy.applyPostLayoutPolicyLw(win2, win2.mAttrs, null, null);
+        displayPolicy.applyPostLayoutPolicyLw(win1, win1.mAttrs, null, null);
+        displayPolicy.finishPostLayoutPolicyLw();
+
+        // Make sure win2 doesn't have visible IME insets, but win1 still does.
+        assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertFalse(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertTrue(win1.getWindowFrames().hasInsetsChanged());
+
+        win2.mAttrs.privateFlags &= ~PRIVATE_FLAG_CONSUME_IME_INSETS;
+        win2.getWindowFrames().setInsetsChanged(false);
+
+        displayPolicy.beginPostLayoutPolicyLw();
+        displayPolicy.applyPostLayoutPolicyLw(win2, win2.mAttrs, null, null);
+        displayPolicy.applyPostLayoutPolicyLw(win1, win1.mAttrs, null, null);
+        displayPolicy.finishPostLayoutPolicyLw();
+
+        // Make sure both windows have visible IME insets.
+        assertTrue(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertTrue(win1.getWindowFrames().hasInsetsChanged());
+    }
+
     private WindowState addNavigationBar() {
         final Binder owner = new Binder();
         final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 49a8886..c9a83b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN;
 import static android.view.SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
@@ -69,15 +70,20 @@
 
     private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
     private static final FrameRateVote FRAME_RATE_VOTE_DENY_LIST =
-            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_LOW_EXACT =
-            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_HI_EXACT =
-            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_LOW_PREFERRED =
-            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_HI_PREFERRED =
-            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
 
     // Parcel and Unparcel the LayoutParams in the window state to test the path the object
     // travels from the app's process to system server
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
deleted file mode 100644
index c5dd447..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * 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.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
-import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.Activity;
-import android.platform.test.annotations.Presubmit;
-import android.server.wm.CtsWindowInfoUtils;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.TrustedPresentationThresholds;
-
-import androidx.annotation.GuardedBy;
-import androidx.test.ext.junit.rules.ActivityScenarioRule;
-
-import com.android.server.wm.utils.CommonUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-
-import java.util.function.Consumer;
-
-/**
- * TODO (b/287076178): Move these tests to
- * {@link android.view.surfacecontrol.cts.TrustedPresentationCallbackTest} when API is made public
- */
-@Presubmit
-public class TrustedPresentationCallbackTest {
-    private static final String TAG = "TrustedPresentationCallbackTest";
-    private static final int STABILITY_REQUIREMENT_MS = 500;
-    private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L;
-
-    private static final float FRACTION_VISIBLE = 0.1f;
-
-    private final Object mResultsLock = new Object();
-    @GuardedBy("mResultsLock")
-    private boolean mResult;
-    @GuardedBy("mResultsLock")
-    private boolean mReceivedResults;
-
-    @Rule
-    public TestName mName = new TestName();
-
-    @Rule
-    public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule(
-            TestActivity.class);
-
-    private TestActivity mActivity;
-
-    @Before
-    public void setup() {
-        mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
-    }
-
-    @After
-    public void tearDown() {
-        CommonUtils.waitUntilActivityRemoved(mActivity);
-    }
-
-    @Test
-    public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException {
-        TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
-                1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
-        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
-                Runnable::run, inTrustedPresentationState -> {
-                    synchronized (mResultsLock) {
-                        mResult = inTrustedPresentationState;
-                        mReceivedResults = true;
-                        mResultsLock.notify();
-                    }
-                });
-        t.apply();
-        synchronized (mResultsLock) {
-            assertResults();
-        }
-    }
-
-    @Test
-    public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException {
-        TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
-                1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
-        Consumer<Boolean> trustedPresentationCallback = inTrustedPresentationState -> {
-            synchronized (mResultsLock) {
-                mResult = inTrustedPresentationState;
-                mReceivedResults = true;
-                mResultsLock.notify();
-            }
-        };
-        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
-                Runnable::run, trustedPresentationCallback);
-        t.apply();
-
-        synchronized (mResultsLock) {
-            if (!mReceivedResults) {
-                mResultsLock.wait(WAIT_TIME_MS);
-            }
-            assertResults();
-            // reset the state
-            mReceivedResults = false;
-        }
-
-        mActivity.getWindow().getRootSurfaceControl().removeTrustedPresentationCallback(t,
-                trustedPresentationCallback);
-        t.apply();
-
-        synchronized (mResultsLock) {
-            if (!mReceivedResults) {
-                mResultsLock.wait(WAIT_TIME_MS);
-            }
-            // Ensure we waited the full time and never received a notify on the result from the
-            // callback.
-            assertFalse("Should never have received a callback", mReceivedResults);
-            // results shouldn't have changed.
-            assertTrue(mResult);
-        }
-    }
-
-    @GuardedBy("mResultsLock")
-    private void assertResults() throws InterruptedException {
-        mResultsLock.wait(WAIT_TIME_MS);
-
-        if (!mReceivedResults) {
-            CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName());
-        }
-        // Make sure we received the results and not just timed out
-        assertTrue("Timed out waiting for results", mReceivedResults);
-        assertTrue(mResult);
-    }
-
-    public static class TestActivity extends Activity {
-    }
-}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6a77b98..7cb2cc3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9892,7 +9892,6 @@
      *
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
-     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
      */
     public static final String
             KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index e12a815..b356fde 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -109,10 +109,6 @@
  * Then for SDK 35+, if the caller identity is personal profile, then
  * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa.
  *
- * <p>If the caller needs to see all subscriptions across user profiles,
- * use {@link #createForAllUserProfiles} to convert the instance to see all. Additional permission
- * may be required as documented on the each API.
- *
  */
 @SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
 @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -1815,9 +1811,6 @@
      * Then for SDK 35+, if the caller identity is personal profile, then this will return
      * subscription 1 only and vice versa.
      *
-     * <p>If the caller needs to see all subscriptions across user profiles,
-     * use {@link #createForAllUserProfiles} to convert this instance to see all.
-     *
      * <p> The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
      * {@link SubscriptionInfo#getSubscriptionId}.
      *
@@ -2085,9 +2078,6 @@
      * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as
      * it does today.
      *
-     * <p>If the caller needs to see all subscriptions across user profiles,
-     * use {@link #createForAllUserProfiles} to convert this instance to see all.
-     *
      * @return The current number of active subscriptions.
      *
      * @see #getActiveSubscriptionInfoList()
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
new file mode 100644
index 0000000..068dfe8
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
@@ -0,0 +1,362 @@
+/*
+ * 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.hoststubgen.nativesubstitution;
+
+import android.os.BadParcelableException;
+import android.os.Parcel;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Native implementation substitutions for the LongArrayMultiStateCounter class.
+ */
+public class LongArrayMultiStateCounter_host {
+
+    /**
+     * A reimplementation of {@link com.android.internal.os.LongArrayMultiStateCounter}, only in
+     * Java instead of native.  The majority of the code (in C++) can be found in
+     * /frameworks/native/libs/battery/MultiStateCounter.h
+     */
+    private static class LongArrayMultiStateCounterRavenwood {
+        private final int mStateCount;
+        private final int mArrayLength;
+        private int mCurrentState;
+        private long mLastStateChangeTimestampMs = -1;
+        private long mLastUpdateTimestampMs = -1;
+        private boolean mEnabled = true;
+
+        private static class State {
+            private long mTimeInStateSinceUpdate;
+            private long[] mCounter;
+        }
+
+        private final State[] mStates;
+        private final long[] mValues;
+        private final long[] mDelta;
+
+        LongArrayMultiStateCounterRavenwood(int stateCount, int arrayLength) {
+            mStateCount = stateCount;
+            mArrayLength = arrayLength;
+            mStates = new State[stateCount];
+            for (int i = 0; i < mStateCount; i++) {
+                mStates[i] = new State();
+                mStates[i].mCounter = new long[mArrayLength];
+            }
+            mValues = new long[mArrayLength];
+            mDelta = new long[mArrayLength];
+        }
+
+        public void setEnabled(boolean enabled, long timestampMs) {
+            if (enabled == mEnabled) {
+                return;
+            }
+
+            if (!enabled) {
+                setState(mCurrentState, timestampMs);
+                mEnabled = false;
+            } else {
+                if (timestampMs < mLastUpdateTimestampMs) {
+                    timestampMs = mLastUpdateTimestampMs;
+                }
+
+                if (mLastStateChangeTimestampMs >= 0) {
+                    mLastStateChangeTimestampMs = timestampMs;
+                }
+                mEnabled = true;
+            }
+        }
+
+        public void setState(int state, long timestampMs) {
+            if (mEnabled && mLastStateChangeTimestampMs >= 0 && mLastUpdateTimestampMs >= 0) {
+                if (timestampMs < mLastUpdateTimestampMs) {
+                    timestampMs = mLastUpdateTimestampMs;
+                }
+
+                if (timestampMs >= mLastStateChangeTimestampMs) {
+                    mStates[mCurrentState].mTimeInStateSinceUpdate +=
+                            timestampMs - mLastStateChangeTimestampMs;
+                } else {
+                    for (int i = 0; i < mStateCount; i++) {
+                        mStates[i].mTimeInStateSinceUpdate = 0;
+                    }
+                }
+            }
+            mCurrentState = state;
+            mLastStateChangeTimestampMs = timestampMs;
+        }
+
+        public void updateValue(long[] values, long timestampMs) {
+            if (mEnabled || mLastUpdateTimestampMs < mLastStateChangeTimestampMs) {
+                if (timestampMs < mLastStateChangeTimestampMs) {
+                    timestampMs = mLastStateChangeTimestampMs;
+                }
+
+                setState(mCurrentState, timestampMs);
+
+                if (mLastUpdateTimestampMs >= 0) {
+                    if (timestampMs > mLastUpdateTimestampMs) {
+                        if (delta(mValues, values, mDelta)) {
+                            long timeSinceUpdate = timestampMs - mLastUpdateTimestampMs;
+                            for (int i = 0; i < mStateCount; i++) {
+                                long timeInState = mStates[i].mTimeInStateSinceUpdate;
+                                if (timeInState > 0) {
+                                    add(mStates[i].mCounter, mDelta, timeInState, timeSinceUpdate);
+                                    mStates[i].mTimeInStateSinceUpdate = 0;
+                                }
+                            }
+                        } else {
+                            throw new RuntimeException();
+                        }
+                    } else if (timestampMs < mLastUpdateTimestampMs) {
+                        throw new RuntimeException();
+                    }
+                }
+            }
+            System.arraycopy(values, 0, mValues, 0, mArrayLength);
+            mLastUpdateTimestampMs = timestampMs;
+        }
+
+        public void incrementValues(long[] delta, long timestampMs) {
+            long[] values = Arrays.copyOf(mValues, mValues.length);
+            for (int i = 0; i < mArrayLength; i++) {
+                values[i] += delta[i];
+            }
+            updateValue(values, timestampMs);
+        }
+
+        public void getValues(long[] values, int state) {
+            System.arraycopy(mStates[state].mCounter, 0, values, 0, mArrayLength);
+        }
+
+        public void reset() {
+            mLastStateChangeTimestampMs = -1;
+            mLastUpdateTimestampMs = -1;
+            for (int i = 0; i < mStateCount; i++) {
+                mStates[i].mTimeInStateSinceUpdate = 0;
+                Arrays.fill(mStates[i].mCounter, 0);
+            }
+        }
+
+        public void writeToParcel(Parcel parcel) {
+            parcel.writeInt(mStateCount);
+            parcel.writeInt(mArrayLength);
+            for (int i = 0; i < mStateCount; i++) {
+                parcel.writeLongArray(mStates[i].mCounter);
+            }
+        }
+
+        public void initFromParcel(Parcel parcel) {
+            try {
+                for (int i = 0; i < mStateCount; i++) {
+                    parcel.readLongArray(mStates[i].mCounter);
+                }
+            } catch (Exception e) {
+                throw new BadParcelableException(e);
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("[");
+            for (int state = 0; state < mStateCount; state++) {
+                if (state != 0) {
+                    sb.append(", ");
+                }
+                sb.append(state).append(": {");
+                for (int i = 0; i < mStates[state].mCounter.length; i++) {
+                    if (i != 0) {
+                        sb.append(", ");
+                    }
+                    sb.append(mStates[state].mCounter[i]);
+                }
+                sb.append("}");
+            }
+            sb.append("]");
+            if (mLastUpdateTimestampMs >= 0) {
+                sb.append(" updated: ").append(mLastUpdateTimestampMs);
+            }
+            if (mLastStateChangeTimestampMs >= 0) {
+                sb.append(" currentState: ").append(mCurrentState);
+                if (mLastStateChangeTimestampMs > mLastUpdateTimestampMs) {
+                    sb.append(" stateChanged: ").append(mLastStateChangeTimestampMs);
+                }
+            } else {
+                sb.append(" currentState: none");
+            }
+            return sb.toString();
+        }
+
+        private boolean delta(long[] values1, long[] values2, long[] delta) {
+            if (delta.length != mArrayLength) {
+                throw new RuntimeException();
+            }
+
+            boolean is_delta_valid = true;
+            for (int i = 0; i < mArrayLength; i++) {
+                if (values2[i] >= values1[i]) {
+                    delta[i] = values2[i] - values1[i];
+                } else {
+                    delta[i] = 0;
+                    is_delta_valid = false;
+                }
+            }
+
+            return is_delta_valid;
+        }
+
+        private void add(long[] counter, long[] delta, long numerator, long denominator) {
+            if (numerator != denominator) {
+                for (int i = 0; i < mArrayLength; i++) {
+                    counter[i] += delta[i] * numerator / denominator;
+                }
+            } else {
+                for (int i = 0; i < mArrayLength; i++) {
+                    counter[i] += delta[i];
+                }
+            }
+        }
+    }
+
+    public static class LongArrayContainer_host {
+        private static final HashMap<Long, long[]> sInstances = new HashMap<>();
+        private static long sNextId = 1;
+
+        public static long native_init(int arrayLength) {
+            long[] array = new long[arrayLength];
+            long instanceId = sNextId++;
+            sInstances.put(instanceId, array);
+            return instanceId;
+        }
+
+        static long[] getInstance(long instanceId) {
+            return sInstances.get(instanceId);
+        }
+
+        public static void native_setValues(long instanceId, long[] values) {
+            System.arraycopy(values, 0, getInstance(instanceId), 0, values.length);
+        }
+
+        public static void native_getValues(long instanceId, long[] values) {
+            System.arraycopy(getInstance(instanceId), 0, values, 0, values.length);
+        }
+
+        public static boolean native_combineValues(long instanceId, long[] array, int[] indexMap) {
+            long[] values = getInstance(instanceId);
+
+            boolean nonZero = false;
+            Arrays.fill(array, 0);
+
+            for (int i = 0; i < values.length; i++) {
+                int index = indexMap[i];
+                if (index < 0 || index >= array.length) {
+                    throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: [0, "
+                                                        + (array.length - 1) + "]");
+                }
+                if (values[i] != 0) {
+                    array[index] += values[i];
+                    nonZero = true;
+                }
+            }
+            return nonZero;
+        }
+    }
+
+    private static final HashMap<Long, LongArrayMultiStateCounterRavenwood> sInstances =
+            new HashMap<>();
+    private static long sNextId = 1;
+
+    public static long native_init(int stateCount, int arrayLength) {
+        LongArrayMultiStateCounterRavenwood instance = new LongArrayMultiStateCounterRavenwood(
+                stateCount, arrayLength);
+        long instanceId = sNextId++;
+        sInstances.put(instanceId, instance);
+        return instanceId;
+    }
+
+    private static LongArrayMultiStateCounterRavenwood getInstance(long instanceId) {
+        return sInstances.get(instanceId);
+    }
+
+    public static void native_setEnabled(long instanceId, boolean enabled,
+            long timestampMs) {
+        getInstance(instanceId).setEnabled(enabled, timestampMs);
+    }
+
+    public static int native_getStateCount(long instanceId) {
+        return getInstance(instanceId).mStateCount;
+    }
+
+    public static int native_getArrayLength(long instanceId) {
+        return getInstance(instanceId).mArrayLength;
+    }
+
+    public static void native_updateValues(long instanceId, long containerInstanceId,
+            long timestampMs) {
+        getInstance(instanceId).updateValue(
+                LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+    }
+
+    public static void native_setState(long instanceId, int state, long timestampMs) {
+        getInstance(instanceId).setState(state, timestampMs);
+    }
+
+    public static void native_incrementValues(long instanceId, long containerInstanceId,
+            long timestampMs) {
+        getInstance(instanceId).incrementValues(
+                LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+    }
+
+    public static void native_getCounts(long instanceId, long containerInstanceId, int state) {
+        getInstance(instanceId).getValues(LongArrayContainer_host.getInstance(containerInstanceId),
+                state);
+    }
+
+    public static void native_reset(long instanceId) {
+        getInstance(instanceId).reset();
+    }
+
+    public static void native_writeToParcel(long instanceId, Parcel parcel, int flags) {
+        getInstance(instanceId).writeToParcel(parcel);
+    }
+
+    public static long native_initFromParcel(Parcel parcel) {
+        int stateCount = parcel.readInt();
+        if (stateCount < 0 || stateCount > 0xEFFF) {
+            throw new BadParcelableException("stateCount out of range");
+        }
+        // LongArrayMultiStateCounter.cpp uses AParcel, which throws on out-of-data.
+        if (parcel.dataPosition() >= parcel.dataSize()) {
+            throw new RuntimeException("Bad parcel");
+        }
+        int arrayLength = parcel.readInt();
+        if (parcel.dataPosition() >= parcel.dataSize()) {
+            throw new RuntimeException("Bad parcel");
+        }
+        long instanceId = native_init(stateCount, arrayLength);
+        getInstance(instanceId).initFromParcel(parcel);
+        if (parcel.dataPosition() > parcel.dataSize()) {
+            throw new RuntimeException("Bad parcel");
+        }
+        return instanceId;
+    }
+
+    public static String native_toString(long instanceId) {
+        return getInstance(instanceId).toString();
+    }
+}