Merge "Use correct focus token for SCVH#getFocusGrantToken" into udc-dev
diff --git a/Android.bp b/Android.bp
index cff863b..64d2c66 100644
--- a/Android.bp
+++ b/Android.bp
@@ -410,6 +410,7 @@
         "spatializer-aidl-java",
         "audiopolicy-aidl-java",
         "sounddose-aidl-java",
+        "modules-utils-expresslog",
     ],
 }
 
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
index f56e1ee..36174c6 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
@@ -20,6 +20,7 @@
 import android.app.JobSchedulerImpl;
 import android.app.SystemServiceRegistry;
 import android.app.tare.EconomyManager;
+import android.app.tare.IEconomyManager;
 import android.content.Context;
 import android.os.DeviceIdleManager;
 import android.os.IDeviceIdleController;
@@ -58,6 +59,7 @@
                 Context.POWER_EXEMPTION_SERVICE, PowerExemptionManager.class,
                 PowerExemptionManager::new);
         SystemServiceRegistry.registerStaticService(
-                Context.RESOURCE_ECONOMY_SERVICE, EconomyManager.class, EconomyManager::new);
+                Context.RESOURCE_ECONOMY_SERVICE, EconomyManager.class,
+                (b) -> new EconomyManager(IEconomyManager.Stub.asInterface(b)));
     }
 }
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
index 581ea7a..0bea028 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
+++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
@@ -19,7 +19,9 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.Context;
+import android.os.RemoteException;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -30,6 +32,7 @@
  *
  * @hide
  */
+@TestApi
 @SystemService(Context.RESOURCE_ECONOMY_SERVICE)
 public class EconomyManager {
     private static final String TAG = "TARE-" + EconomyManager.class.getSimpleName();
@@ -95,13 +98,17 @@
         }
     }
 
-
+    /** @hide */
+    @TestApi
     public static final int ENABLED_MODE_OFF = 0;
+    /** @hide */
     public static final int ENABLED_MODE_ON = 1;
     /**
      * Go through the motions, tracking events, updating balances and other TARE state values,
      * but don't use TARE to affect actual device behavior.
+     * @hide
      */
+    @TestApi
     public static final int ENABLED_MODE_SHADOW = 2;
 
     /** @hide */
@@ -114,6 +121,7 @@
     public @interface EnabledMode {
     }
 
+    /** @hide */
     public static String enabledModeToString(@EnabledMode int mode) {
         switch (mode) {
             case ENABLED_MODE_OFF: return "ENABLED_MODE_OFF";
@@ -123,11 +131,18 @@
         }
     }
 
+    /** @hide */
+    @TestApi
     public static final String KEY_ENABLE_TARE_MODE = "enable_tare_mode";
+    /** @hide */
     public static final String KEY_ENABLE_POLICY_ALARM = "enable_policy_alarm";
+    /** @hide */
     public static final String KEY_ENABLE_POLICY_JOB_SCHEDULER = "enable_policy_job";
+    /** @hide */
     public static final int DEFAULT_ENABLE_TARE_MODE = ENABLED_MODE_OFF;
+    /** @hide */
     public static final boolean DEFAULT_ENABLE_POLICY_ALARM = true;
+    /** @hide */
     public static final boolean DEFAULT_ENABLE_POLICY_JOB_SCHEDULER = true;
 
     // Keys for AlarmManager TARE factors
@@ -612,4 +627,27 @@
     public static final long DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE_CAKES = arcToCake(1);
     /** @hide */
     public static final long DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES = arcToCake(60);
+
+    //////// APIs below ////////
+
+    private final IEconomyManager mService;
+
+    /** @hide */
+    public EconomyManager(IEconomyManager service) {
+        mService = service;
+    }
+
+    /**
+     * Returns the current enabled status of TARE.
+     * @hide
+     */
+    @EnabledMode
+    @TestApi
+    public int getEnabledMode() {
+        try {
+            return mService.getEnabledMode();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl b/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl
index bb15011..2be0db7 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl
+++ b/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl
@@ -21,4 +21,5 @@
   * {@hide}
   */
 interface IEconomyManager {
+    int getEnabledMode();
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index aef9dd0..3aec8ba 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1588,12 +1588,12 @@
         final ArrayMap<String, List<JobInfo>> outMap = new ArrayMap<>();
         synchronized (mLock) {
             ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
-            // Write out for loop to avoid addAll() creating an Iterator.
+            // Write out for loop to avoid creating an Iterator.
             for (int i = jobs.size() - 1; i >= 0; i--) {
                 final JobStatus job = jobs.valueAt(i);
                 List<JobInfo> outList = outMap.get(job.getNamespace());
                 if (outList == null) {
-                    outList = new ArrayList<JobInfo>(jobs.size());
+                    outList = new ArrayList<>();
                     outMap.put(job.getNamespace(), outList);
                 }
 
@@ -1606,7 +1606,7 @@
     private List<JobInfo> getPendingJobsInNamespace(int uid, @Nullable String namespace) {
         synchronized (mLock) {
             ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
-            ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
+            ArrayList<JobInfo> outList = new ArrayList<>();
             // Write out for loop to avoid addAll() creating an Iterator.
             for (int i = jobs.size() - 1; i >= 0; i--) {
                 final JobStatus job = jobs.valueAt(i);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
index fc60228..ba62e96 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
@@ -31,7 +31,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
 
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 7f6a75e..c707069 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -1351,6 +1351,11 @@
         }
 
         @Override
+        public int getEnabledMode() {
+            return InternalResourceService.this.getEnabledMode();
+        }
+
+        @Override
         public int handleShellCommand(@NonNull ParcelFileDescriptor in,
                 @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
                 @NonNull String[] args) {
diff --git a/boot/boot-image-profile.txt b/boot/boot-image-profile.txt
index 996c388..aebace5 100644
--- a/boot/boot-image-profile.txt
+++ b/boot/boot-image-profile.txt
@@ -33583,8 +33583,8 @@
 Lcom/android/internal/dynamicanimation/animation/Force;
 Lcom/android/internal/dynamicanimation/animation/SpringAnimation;
 Lcom/android/internal/dynamicanimation/animation/SpringForce;
-Lcom/android/internal/expresslog/Counter;
-Lcom/android/internal/expresslog/Utils;
+Lcom/android/modules/expresslog/Counter;
+Lcom/android/modules/expresslog/Utils;
 Lcom/android/internal/graphics/ColorUtils$ContrastCalculator;
 Lcom/android/internal/graphics/ColorUtils;
 Lcom/android/internal/graphics/SfVsyncFrameCallbackProvider;
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index 21ae134..4293caf 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -10784,8 +10784,8 @@
 com.android.internal.dynamicanimation.animation.Force
 com.android.internal.dynamicanimation.animation.SpringAnimation
 com.android.internal.dynamicanimation.animation.SpringForce
-com.android.internal.expresslog.Counter
-com.android.internal.expresslog.Utils
+com.android.modules.expresslog.Counter
+com.android.modules.expresslog.Utils
 com.android.internal.graphics.ColorUtils$ContrastCalculator
 com.android.internal.graphics.ColorUtils
 com.android.internal.graphics.SfVsyncFrameCallbackProvider
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 6998081..b6dc32a 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -21,6 +21,7 @@
 import android.app.backup.BackupManager;
 import android.app.backup.BackupManagerMonitor;
 import android.app.backup.BackupProgress;
+import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.BackupTransport;
 import android.app.backup.IBackupManager;
 import android.app.backup.IBackupManagerMonitor;
@@ -821,14 +822,22 @@
             doRestorePackage(arg);
         } else {
             try {
+                @Monitor int monitor = Monitor.OFF;
+
                 long token = Long.parseLong(arg, 16);
                 HashSet<String> filter = null;
                 while ((arg = nextArg()) != null) {
-                    if (filter == null) filter = new HashSet<String>();
-                    filter.add(arg);
+                    if (arg.equals("--monitor")) {
+                        monitor = Monitor.NORMAL;
+                    } else if (arg.equals("--monitor-verbose")) {
+                        monitor = Monitor.VERBOSE;
+                    } else {
+                        if (filter == null) filter = new HashSet<String>();
+                        filter.add(arg);
+                    }
                 }
 
-                doRestoreAll(userId, token, filter);
+                doRestoreAll(userId, token, filter, monitor);
             } catch (NumberFormatException e) {
                 showUsage();
                 return;
@@ -841,7 +850,8 @@
         System.err.println("'restore <token> <package>'.");
     }
 
-    private void doRestoreAll(@UserIdInt int userId, long token, HashSet<String> filter) {
+    private void doRestoreAll(@UserIdInt int userId, long token, HashSet<String> filter,
+            @Monitor int monitorState) {
         RestoreObserver observer = new RestoreObserver();
 
         try {
@@ -852,8 +862,11 @@
                 return;
             }
             RestoreSet[] sets = null;
-            // TODO implement monitor here
-            int err = mRestore.getAvailableRestoreSets(observer, null);
+            BackupMonitor monitor =
+                    (monitorState != Monitor.OFF)
+                            ? new BackupMonitor(monitorState == Monitor.VERBOSE)
+                            : null;
+            int err = mRestore.getAvailableRestoreSets(observer, monitor);
             if (err == 0) {
                 observer.waitForCompletion();
                 sets = observer.sets;
@@ -862,12 +875,12 @@
                         if (s.token == token) {
                             System.out.println("Scheduling restore: " + s.name);
                             if (filter == null) {
-                                didRestore = (mRestore.restoreAll(token, observer, null) == 0);
+                                didRestore = (mRestore.restoreAll(token, observer, monitor) == 0);
                             } else {
                                 String[] names = new String[filter.size()];
                                 filter.toArray(names);
                                 didRestore = (mRestore.restorePackages(token, observer, names,
-                                        null) == 0);
+                                        monitor) == 0);
                             }
                             break;
                         }
@@ -958,8 +971,8 @@
         System.err.println("       bmgr list transports [-c]");
         System.err.println("       bmgr list sets");
         System.err.println("       bmgr transport WHICH|-c WHICH_COMPONENT");
-        System.err.println("       bmgr restore TOKEN");
-        System.err.println("       bmgr restore TOKEN PACKAGE...");
+        System.err.println("       bmgr restore TOKEN [--monitor|--monitor-verbose]");
+        System.err.println("       bmgr restore TOKEN PACKAGE... [--monitor|--monitor-verbose]");
         System.err.println("       bmgr run");
         System.err.println("       bmgr wipe TRANSPORT PACKAGE");
         System.err.println("       bmgr fullbackup PACKAGE...");
@@ -1005,12 +1018,18 @@
         System.err.println("restore operation from the currently active transport.  It will deliver");
         System.err.println("the restore set designated by the TOKEN argument to each application");
         System.err.println("that had contributed data to that restore set.");
+        System.err.println("    --monitor flag prints monitor events (important events and errors");
+        System.err.println("              encountered during restore).");
+        System.err.println("    --monitor-verbose flag prints monitor events with all keys.");
         System.err.println("");
         System.err.println("The 'restore' command when given a token and one or more package names");
         System.err.println("initiates a restore operation of just those given packages from the restore");
         System.err.println("set designated by the TOKEN argument.  It is effectively the same as the");
         System.err.println("'restore' operation supplying only a token, but applies a filter to the");
         System.err.println("set of applications to be restored.");
+        System.err.println("    --monitor flag prints monitor events (important events and errors");
+        System.err.println("              encountered during restore).");
+        System.err.println("    --monitor-verbose flag prints monitor events with all keys.");
         System.err.println("");
         System.err.println("The 'run' command causes any scheduled backup operation to be initiated");
         System.err.println("immediately, without the usual waiting period for batching together");
@@ -1026,7 +1045,8 @@
         System.err.println("");
         System.err.println("The 'backupnow' command runs an immediate backup for one or more packages.");
         System.err.println("    --all flag runs backup for all eligible packages.");
-        System.err.println("    --monitor flag prints monitor events.");
+        System.err.println("    --monitor flag prints monitor events (important events and errors");
+        System.err.println("              encountered during backup).");
         System.err.println("    --monitor-verbose flag prints monitor events with all keys.");
         System.err.println("For each package it will run key/value or full data backup ");
         System.err.println("depending on the package's manifest declarations.");
@@ -1076,6 +1096,37 @@
                     out.append("(v").append(version).append(")");
                 }
             }
+            if (event.containsKey(BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS)) {
+                ArrayList<BackupRestoreEventLogger.DataTypeResult> results =
+                        event.getParcelableArrayList(
+                                BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS,
+                                BackupRestoreEventLogger.DataTypeResult.class);
+                out.append(", results = [");
+                for (BackupRestoreEventLogger.DataTypeResult result : results) {
+                    out.append("\n{\n\tdataType: ");
+                    out.append(result.getDataType());
+                    out.append("\n\tsuccessCount: ");
+                    out.append(result.getSuccessCount());
+                    out.append("\n\tfailCount: ");
+                    out.append(result.getFailCount());
+                    out.append("\n\tmetadataHash: ");
+                    out.append(Arrays.toString(result.getMetadataHash()));
+
+                    if (!result.getErrors().isEmpty()) {
+                        out.append("\n\terrors: [");
+                        for (String error : result.getErrors().keySet()) {
+                            out.append(error);
+                            out.append(": ");
+                            out.append(result.getErrors().get(error));
+                            out.append(";");
+                        }
+                        out.append("]");
+                    }
+                    out.append("\n}");
+
+                }
+                out.append("]");
+            }
             if (mVerbose) {
                 Set<String> remainingKeys = new ArraySet<>(event.keySet());
                 remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_ID);
@@ -1083,6 +1134,7 @@
                 remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME);
                 remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION);
                 remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION);
+                remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS);
                 if (!remainingKeys.isEmpty()) {
                     out.append(", other keys =");
                     for (String key : remainingKeys) {
@@ -1192,6 +1244,8 @@
                 return "NO_PACKAGES";
             case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL:
                 return "TRANSPORT_IS_NULL";
+            case BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS:
+                return "AGENT_LOGGING_RESULTS";
             default:
                 return "UNKNOWN_ID";
         }
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index c4e8b0e..a8b6c0b 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -105,6 +105,7 @@
 static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
 static const char CLOCK_ENABLED_PROP_NAME[] = "persist.sys.bootanim.clock.enabled";
 static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
+static const int MAX_CHECK_EXIT_INTERVAL_US = 50000;
 static constexpr size_t TEXT_POS_LEN_MAX = 16;
 static const int DYNAMIC_COLOR_COUNT = 4;
 static const char U_TEXTURE[] = "uTexture";
@@ -1678,7 +1679,17 @@
                 checkExit();
             }
 
-            usleep(part.pause * ns2us(frameDuration));
+            int pauseDuration = part.pause * ns2us(frameDuration);
+            while(pauseDuration > 0 && !exitPending()){
+                if (pauseDuration > MAX_CHECK_EXIT_INTERVAL_US) {
+                    usleep(MAX_CHECK_EXIT_INTERVAL_US);
+                    pauseDuration -= MAX_CHECK_EXIT_INTERVAL_US;
+                } else {
+                    usleep(pauseDuration);
+                    break;
+                }
+                checkExit();
+            }
 
             if (exitPending() && !part.count && mCurrentInset >= mTargetInset &&
                 !part.hasFadingPhase()) {
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 3cc9908..bb07487 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -43717,8 +43717,8 @@
 Lcom/android/internal/dynamicanimation/animation/Force;
 Lcom/android/internal/dynamicanimation/animation/SpringAnimation;
 Lcom/android/internal/dynamicanimation/animation/SpringForce;
-Lcom/android/internal/expresslog/Counter;
-Lcom/android/internal/expresslog/Utils;
+Lcom/android/modules/expresslog/Counter;
+Lcom/android/modules/expresslog/Utils;
 Lcom/android/internal/graphics/ColorUtils$ContrastCalculator;
 Lcom/android/internal/graphics/ColorUtils;
 Lcom/android/internal/graphics/SfVsyncFrameCallbackProvider;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 8e50fe8..1812c2b 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -10815,8 +10815,8 @@
 com.android.internal.dynamicanimation.animation.Force
 com.android.internal.dynamicanimation.animation.SpringAnimation
 com.android.internal.dynamicanimation.animation.SpringForce
-com.android.internal.expresslog.Counter
-com.android.internal.expresslog.Utils
+com.android.modules.expresslog.Counter
+com.android.modules.expresslog.Utils
 com.android.internal.graphics.ColorUtils$ContrastCalculator
 com.android.internal.graphics.ColorUtils
 com.android.internal.graphics.SfVsyncFrameCallbackProvider
diff --git a/core/api/current.txt b/core/api/current.txt
index 80abd84..288ab47 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -377,7 +377,7 @@
   public static final class R.attr {
     ctor public R.attr();
     field public static final int absListViewStyle = 16842858; // 0x101006a
-    field public static final int accessibilityDataSensitive;
+    field public static final int accessibilityDataSensitive = 16844407; // 0x1010677
     field public static final int accessibilityEventTypes = 16843648; // 0x1010380
     field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
     field public static final int accessibilityFlags = 16843652; // 0x1010384
@@ -445,12 +445,12 @@
     field public static final int allowGameFpsOverride = 16844378; // 0x101065a
     field public static final int allowNativeHeapPointerTagging = 16844306; // 0x1010612
     field public static final int allowParallelSyncs = 16843570; // 0x1010332
-    field public static final int allowSharedIsolatedProcess;
+    field public static final int allowSharedIsolatedProcess = 16844413; // 0x101067d
     field public static final int allowSingleTap = 16843353; // 0x1010259
     field public static final int allowTaskReparenting = 16843268; // 0x1010204
     field public static final int allowUndo = 16843999; // 0x10104df
     field public static final int allowUntrustedActivityEmbedding = 16844393; // 0x1010669
-    field public static final int allowUpdateOwnership;
+    field public static final int allowUpdateOwnership = 16844416; // 0x1010680
     field public static final int alpha = 16843551; // 0x101031f
     field public static final int alphabeticModifiers = 16844110; // 0x101054e
     field public static final int alphabeticShortcut = 16843235; // 0x10101e3
@@ -556,7 +556,7 @@
     field public static final int canTakeScreenshot = 16844303; // 0x101060f
     field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
     field public static final int cantSaveState = 16844142; // 0x101056e
-    field public static final int capability;
+    field public static final int capability = 16844423; // 0x1010687
     field @Deprecated public static final int capitalize = 16843113; // 0x1010169
     field public static final int category = 16843752; // 0x10103e8
     field public static final int centerBright = 16842956; // 0x10100cc
@@ -741,7 +741,7 @@
     field public static final int ellipsize = 16842923; // 0x10100ab
     field public static final int ems = 16843096; // 0x1010158
     field public static final int enableOnBackInvokedCallback = 16844396; // 0x101066c
-    field public static final int enableTextStylingShortcuts;
+    field public static final int enableTextStylingShortcuts = 16844408; // 0x1010678
     field public static final int enableVrMode = 16844069; // 0x1010525
     field public static final int enabled = 16842766; // 0x101000e
     field public static final int end = 16843996; // 0x10104dc
@@ -810,7 +810,7 @@
     field public static final int focusableInTouchMode = 16842971; // 0x10100db
     field public static final int focusedByDefault = 16844100; // 0x1010544
     field @Deprecated public static final int focusedMonthDateColor = 16843587; // 0x1010343
-    field public static final int focusedSearchResultHighlightColor;
+    field public static final int focusedSearchResultHighlightColor = 16844419; // 0x1010683
     field public static final int font = 16844082; // 0x1010532
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
@@ -896,10 +896,10 @@
     field public static final int hand_secondTintMode = 16844349; // 0x101063d
     field public static final int handle = 16843354; // 0x101025a
     field public static final int handleProfiling = 16842786; // 0x1010022
-    field public static final int handwritingBoundsOffsetBottom;
-    field public static final int handwritingBoundsOffsetLeft;
-    field public static final int handwritingBoundsOffsetRight;
-    field public static final int handwritingBoundsOffsetTop;
+    field public static final int handwritingBoundsOffsetBottom = 16844406; // 0x1010676
+    field public static final int handwritingBoundsOffsetLeft = 16844403; // 0x1010673
+    field public static final int handwritingBoundsOffsetRight = 16844405; // 0x1010675
+    field public static final int handwritingBoundsOffsetTop = 16844404; // 0x1010674
     field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e
     field public static final int hardwareAccelerated = 16843475; // 0x10102d3
     field public static final int hasCode = 16842764; // 0x101000c
@@ -986,7 +986,7 @@
     field public static final int isAlwaysSyncable = 16843571; // 0x1010333
     field public static final int isAsciiCapable = 16843753; // 0x10103e9
     field public static final int isAuxiliary = 16843647; // 0x101037f
-    field public static final int isCredential;
+    field public static final int isCredential = 16844417; // 0x1010681
     field public static final int isDefault = 16843297; // 0x1010221
     field public static final int isFeatureSplit = 16844123; // 0x101055b
     field public static final int isGame = 16843764; // 0x10103f4
@@ -1021,8 +1021,8 @@
     field @Deprecated public static final int keyTextSize = 16843316; // 0x1010234
     field @Deprecated public static final int keyWidth = 16843325; // 0x101023d
     field public static final int keyboardLayout = 16843691; // 0x10103ab
-    field public static final int keyboardLayoutType;
-    field public static final int keyboardLocale;
+    field public static final int keyboardLayoutType = 16844415; // 0x101067f
+    field public static final int keyboardLocale = 16844414; // 0x101067e
     field @Deprecated public static final int keyboardMode = 16843341; // 0x101024d
     field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
     field public static final int keycode = 16842949; // 0x10100c5
@@ -1263,8 +1263,8 @@
     field public static final int persistentDrawingCache = 16842990; // 0x10100ee
     field public static final int persistentWhenFeatureAvailable = 16844131; // 0x1010563
     field @Deprecated public static final int phoneNumber = 16843111; // 0x1010167
-    field public static final int physicalKeyboardHintLanguageTag;
-    field public static final int physicalKeyboardHintLayoutType;
+    field public static final int physicalKeyboardHintLanguageTag = 16844411; // 0x101067b
+    field public static final int physicalKeyboardHintLayoutType = 16844412; // 0x101067c
     field public static final int pivotX = 16843189; // 0x10101b5
     field public static final int pivotY = 16843190; // 0x10101b6
     field public static final int pointerIcon = 16844041; // 0x1010509
@@ -1354,7 +1354,7 @@
     field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
     field public static final int required = 16843406; // 0x101028e
     field public static final int requiredAccountType = 16843734; // 0x10103d6
-    field public static final int requiredDisplayCategory;
+    field public static final int requiredDisplayCategory = 16844409; // 0x1010679
     field public static final int requiredFeature = 16844116; // 0x1010554
     field public static final int requiredForAllUsers = 16843728; // 0x10103d0
     field public static final int requiredNotFeature = 16844117; // 0x1010555
@@ -1422,7 +1422,7 @@
     field public static final int searchHintIcon = 16843988; // 0x10104d4
     field public static final int searchIcon = 16843907; // 0x1010483
     field public static final int searchMode = 16843221; // 0x10101d5
-    field public static final int searchResultHighlightColor;
+    field public static final int searchResultHighlightColor = 16844418; // 0x1010682
     field public static final int searchSettingsDescription = 16843402; // 0x101028a
     field public static final int searchSuggestAuthority = 16843222; // 0x10101d6
     field public static final int searchSuggestIntentAction = 16843225; // 0x10101d9
@@ -1449,7 +1449,7 @@
     field public static final int sessionService = 16843837; // 0x101043d
     field public static final int settingsActivity = 16843301; // 0x1010225
     field public static final int settingsSliceUri = 16844179; // 0x1010593
-    field public static final int settingsSubtitle;
+    field public static final int settingsSubtitle = 16844422; // 0x1010686
     field public static final int setupActivity = 16843766; // 0x10103f6
     field public static final int shadowColor = 16843105; // 0x1010161
     field public static final int shadowDx = 16843106; // 0x1010162
@@ -1555,7 +1555,7 @@
     field public static final int strokeLineJoin = 16843788; // 0x101040c
     field public static final int strokeMiterLimit = 16843789; // 0x101040d
     field public static final int strokeWidth = 16843783; // 0x1010407
-    field public static final int stylusHandwritingSettingsActivity;
+    field public static final int stylusHandwritingSettingsActivity = 16844420; // 0x1010684
     field public static final int subMenuArrow = 16844019; // 0x10104f3
     field public static final int submitBackground = 16843912; // 0x1010488
     field public static final int subtitle = 16843473; // 0x10102d1
@@ -1861,7 +1861,7 @@
     field public static final int windowMinWidthMajor = 16843606; // 0x1010356
     field public static final int windowMinWidthMinor = 16843607; // 0x1010357
     field public static final int windowNoDisplay = 16843294; // 0x101021e
-    field public static final int windowNoMoveAnimation;
+    field public static final int windowNoMoveAnimation = 16844421; // 0x1010685
     field public static final int windowNoTitle = 16842838; // 0x1010056
     field @Deprecated public static final int windowOverscan = 16843727; // 0x10103cf
     field public static final int windowReenterTransition = 16843951; // 0x10104af
@@ -1966,18 +1966,18 @@
     field public static final int system_accent3_700 = 17170522; // 0x106005a
     field public static final int system_accent3_800 = 17170523; // 0x106005b
     field public static final int system_accent3_900 = 17170524; // 0x106005c
-    field public static final int system_background_dark;
-    field public static final int system_background_light;
-    field public static final int system_control_activated_dark;
-    field public static final int system_control_activated_light;
-    field public static final int system_control_highlight_dark;
-    field public static final int system_control_highlight_light;
-    field public static final int system_control_normal_dark;
-    field public static final int system_control_normal_light;
-    field public static final int system_error_container_dark;
-    field public static final int system_error_container_light;
-    field public static final int system_error_dark;
-    field public static final int system_error_light;
+    field public static final int system_background_dark = 17170581; // 0x1060095
+    field public static final int system_background_light = 17170538; // 0x106006a
+    field public static final int system_control_activated_dark = 17170599; // 0x10600a7
+    field public static final int system_control_activated_light = 17170556; // 0x106007c
+    field public static final int system_control_highlight_dark = 17170601; // 0x10600a9
+    field public static final int system_control_highlight_light = 17170558; // 0x106007e
+    field public static final int system_control_normal_dark = 17170600; // 0x10600a8
+    field public static final int system_control_normal_light = 17170557; // 0x106007d
+    field public static final int system_error_container_dark = 17170597; // 0x10600a5
+    field public static final int system_error_container_light = 17170554; // 0x106007a
+    field public static final int system_error_dark = 17170595; // 0x10600a3
+    field public static final int system_error_light = 17170552; // 0x1060078
     field public static final int system_neutral1_0 = 17170461; // 0x106001d
     field public static final int system_neutral1_10 = 17170462; // 0x106001e
     field public static final int system_neutral1_100 = 17170464; // 0x1060020
@@ -2004,94 +2004,94 @@
     field public static final int system_neutral2_700 = 17170483; // 0x1060033
     field public static final int system_neutral2_800 = 17170484; // 0x1060034
     field public static final int system_neutral2_900 = 17170485; // 0x1060035
-    field public static final int system_on_background_dark;
-    field public static final int system_on_background_light;
-    field public static final int system_on_error_container_dark;
-    field public static final int system_on_error_container_light;
-    field public static final int system_on_error_dark;
-    field public static final int system_on_error_light;
-    field public static final int system_on_primary_container_dark;
-    field public static final int system_on_primary_container_light;
-    field public static final int system_on_primary_dark;
-    field public static final int system_on_primary_fixed;
-    field public static final int system_on_primary_fixed_variant;
-    field public static final int system_on_primary_light;
-    field public static final int system_on_secondary_container_dark;
-    field public static final int system_on_secondary_container_light;
-    field public static final int system_on_secondary_dark;
-    field public static final int system_on_secondary_fixed;
-    field public static final int system_on_secondary_fixed_variant;
-    field public static final int system_on_secondary_light;
-    field public static final int system_on_surface_dark;
-    field public static final int system_on_surface_light;
-    field public static final int system_on_surface_variant_dark;
-    field public static final int system_on_surface_variant_light;
-    field public static final int system_on_tertiary_container_dark;
-    field public static final int system_on_tertiary_container_light;
-    field public static final int system_on_tertiary_dark;
-    field public static final int system_on_tertiary_fixed;
-    field public static final int system_on_tertiary_fixed_variant;
-    field public static final int system_on_tertiary_light;
-    field public static final int system_outline_dark;
-    field public static final int system_outline_light;
-    field public static final int system_outline_variant_dark;
-    field public static final int system_outline_variant_light;
-    field public static final int system_palette_key_color_neutral_dark;
-    field public static final int system_palette_key_color_neutral_light;
-    field public static final int system_palette_key_color_neutral_variant_dark;
-    field public static final int system_palette_key_color_neutral_variant_light;
-    field public static final int system_palette_key_color_primary_dark;
-    field public static final int system_palette_key_color_primary_light;
-    field public static final int system_palette_key_color_secondary_dark;
-    field public static final int system_palette_key_color_secondary_light;
-    field public static final int system_palette_key_color_tertiary_dark;
-    field public static final int system_palette_key_color_tertiary_light;
-    field public static final int system_primary_container_dark;
-    field public static final int system_primary_container_light;
-    field public static final int system_primary_dark;
-    field public static final int system_primary_fixed;
-    field public static final int system_primary_fixed_dim;
-    field public static final int system_primary_light;
-    field public static final int system_secondary_container_dark;
-    field public static final int system_secondary_container_light;
-    field public static final int system_secondary_dark;
-    field public static final int system_secondary_fixed;
-    field public static final int system_secondary_fixed_dim;
-    field public static final int system_secondary_light;
-    field public static final int system_surface_bright_dark;
-    field public static final int system_surface_bright_light;
-    field public static final int system_surface_container_dark;
-    field public static final int system_surface_container_high_dark;
-    field public static final int system_surface_container_high_light;
-    field public static final int system_surface_container_highest_dark;
-    field public static final int system_surface_container_highest_light;
-    field public static final int system_surface_container_light;
-    field public static final int system_surface_container_low_dark;
-    field public static final int system_surface_container_low_light;
-    field public static final int system_surface_container_lowest_dark;
-    field public static final int system_surface_container_lowest_light;
-    field public static final int system_surface_dark;
-    field public static final int system_surface_dim_dark;
-    field public static final int system_surface_dim_light;
-    field public static final int system_surface_light;
-    field public static final int system_surface_variant_dark;
-    field public static final int system_surface_variant_light;
-    field public static final int system_tertiary_container_dark;
-    field public static final int system_tertiary_container_light;
-    field public static final int system_tertiary_dark;
-    field public static final int system_tertiary_fixed;
-    field public static final int system_tertiary_fixed_dim;
-    field public static final int system_tertiary_light;
-    field public static final int system_text_hint_inverse_dark;
-    field public static final int system_text_hint_inverse_light;
-    field public static final int system_text_primary_inverse_dark;
-    field public static final int system_text_primary_inverse_disable_only_dark;
-    field public static final int system_text_primary_inverse_disable_only_light;
-    field public static final int system_text_primary_inverse_light;
-    field public static final int system_text_secondary_and_tertiary_inverse_dark;
-    field public static final int system_text_secondary_and_tertiary_inverse_disabled_dark;
-    field public static final int system_text_secondary_and_tertiary_inverse_disabled_light;
-    field public static final int system_text_secondary_and_tertiary_inverse_light;
+    field public static final int system_on_background_dark = 17170582; // 0x1060096
+    field public static final int system_on_background_light = 17170539; // 0x106006b
+    field public static final int system_on_error_container_dark = 17170598; // 0x10600a6
+    field public static final int system_on_error_container_light = 17170555; // 0x106007b
+    field public static final int system_on_error_dark = 17170596; // 0x10600a4
+    field public static final int system_on_error_light = 17170553; // 0x1060079
+    field public static final int system_on_primary_container_dark = 17170570; // 0x106008a
+    field public static final int system_on_primary_container_light = 17170527; // 0x106005f
+    field public static final int system_on_primary_dark = 17170572; // 0x106008c
+    field public static final int system_on_primary_fixed = 17170614; // 0x10600b6
+    field public static final int system_on_primary_fixed_variant = 17170615; // 0x10600b7
+    field public static final int system_on_primary_light = 17170529; // 0x1060061
+    field public static final int system_on_secondary_container_dark = 17170574; // 0x106008e
+    field public static final int system_on_secondary_container_light = 17170531; // 0x1060063
+    field public static final int system_on_secondary_dark = 17170576; // 0x1060090
+    field public static final int system_on_secondary_fixed = 17170618; // 0x10600ba
+    field public static final int system_on_secondary_fixed_variant = 17170619; // 0x10600bb
+    field public static final int system_on_secondary_light = 17170533; // 0x1060065
+    field public static final int system_on_surface_dark = 17170584; // 0x1060098
+    field public static final int system_on_surface_light = 17170541; // 0x106006d
+    field public static final int system_on_surface_variant_dark = 17170593; // 0x10600a1
+    field public static final int system_on_surface_variant_light = 17170550; // 0x1060076
+    field public static final int system_on_tertiary_container_dark = 17170578; // 0x1060092
+    field public static final int system_on_tertiary_container_light = 17170535; // 0x1060067
+    field public static final int system_on_tertiary_dark = 17170580; // 0x1060094
+    field public static final int system_on_tertiary_fixed = 17170622; // 0x10600be
+    field public static final int system_on_tertiary_fixed_variant = 17170623; // 0x10600bf
+    field public static final int system_on_tertiary_light = 17170537; // 0x1060069
+    field public static final int system_outline_dark = 17170594; // 0x10600a2
+    field public static final int system_outline_light = 17170551; // 0x1060077
+    field public static final int system_outline_variant_dark = 17170625; // 0x10600c1
+    field public static final int system_outline_variant_light = 17170624; // 0x10600c0
+    field public static final int system_palette_key_color_neutral_dark = 17170610; // 0x10600b2
+    field public static final int system_palette_key_color_neutral_light = 17170567; // 0x1060087
+    field public static final int system_palette_key_color_neutral_variant_dark = 17170611; // 0x10600b3
+    field public static final int system_palette_key_color_neutral_variant_light = 17170568; // 0x1060088
+    field public static final int system_palette_key_color_primary_dark = 17170607; // 0x10600af
+    field public static final int system_palette_key_color_primary_light = 17170564; // 0x1060084
+    field public static final int system_palette_key_color_secondary_dark = 17170608; // 0x10600b0
+    field public static final int system_palette_key_color_secondary_light = 17170565; // 0x1060085
+    field public static final int system_palette_key_color_tertiary_dark = 17170609; // 0x10600b1
+    field public static final int system_palette_key_color_tertiary_light = 17170566; // 0x1060086
+    field public static final int system_primary_container_dark = 17170569; // 0x1060089
+    field public static final int system_primary_container_light = 17170526; // 0x106005e
+    field public static final int system_primary_dark = 17170571; // 0x106008b
+    field public static final int system_primary_fixed = 17170612; // 0x10600b4
+    field public static final int system_primary_fixed_dim = 17170613; // 0x10600b5
+    field public static final int system_primary_light = 17170528; // 0x1060060
+    field public static final int system_secondary_container_dark = 17170573; // 0x106008d
+    field public static final int system_secondary_container_light = 17170530; // 0x1060062
+    field public static final int system_secondary_dark = 17170575; // 0x106008f
+    field public static final int system_secondary_fixed = 17170616; // 0x10600b8
+    field public static final int system_secondary_fixed_dim = 17170617; // 0x10600b9
+    field public static final int system_secondary_light = 17170532; // 0x1060064
+    field public static final int system_surface_bright_dark = 17170590; // 0x106009e
+    field public static final int system_surface_bright_light = 17170547; // 0x1060073
+    field public static final int system_surface_container_dark = 17170587; // 0x106009b
+    field public static final int system_surface_container_high_dark = 17170588; // 0x106009c
+    field public static final int system_surface_container_high_light = 17170545; // 0x1060071
+    field public static final int system_surface_container_highest_dark = 17170589; // 0x106009d
+    field public static final int system_surface_container_highest_light = 17170546; // 0x1060072
+    field public static final int system_surface_container_light = 17170544; // 0x1060070
+    field public static final int system_surface_container_low_dark = 17170585; // 0x1060099
+    field public static final int system_surface_container_low_light = 17170542; // 0x106006e
+    field public static final int system_surface_container_lowest_dark = 17170586; // 0x106009a
+    field public static final int system_surface_container_lowest_light = 17170543; // 0x106006f
+    field public static final int system_surface_dark = 17170583; // 0x1060097
+    field public static final int system_surface_dim_dark = 17170591; // 0x106009f
+    field public static final int system_surface_dim_light = 17170548; // 0x1060074
+    field public static final int system_surface_light = 17170540; // 0x106006c
+    field public static final int system_surface_variant_dark = 17170592; // 0x10600a0
+    field public static final int system_surface_variant_light = 17170549; // 0x1060075
+    field public static final int system_tertiary_container_dark = 17170577; // 0x1060091
+    field public static final int system_tertiary_container_light = 17170534; // 0x1060066
+    field public static final int system_tertiary_dark = 17170579; // 0x1060093
+    field public static final int system_tertiary_fixed = 17170620; // 0x10600bc
+    field public static final int system_tertiary_fixed_dim = 17170621; // 0x10600bd
+    field public static final int system_tertiary_light = 17170536; // 0x1060068
+    field public static final int system_text_hint_inverse_dark = 17170606; // 0x10600ae
+    field public static final int system_text_hint_inverse_light = 17170563; // 0x1060083
+    field public static final int system_text_primary_inverse_dark = 17170602; // 0x10600aa
+    field public static final int system_text_primary_inverse_disable_only_dark = 17170604; // 0x10600ac
+    field public static final int system_text_primary_inverse_disable_only_light = 17170561; // 0x1060081
+    field public static final int system_text_primary_inverse_light = 17170559; // 0x106007f
+    field public static final int system_text_secondary_and_tertiary_inverse_dark = 17170603; // 0x10600ab
+    field public static final int system_text_secondary_and_tertiary_inverse_disabled_dark = 17170605; // 0x10600ad
+    field public static final int system_text_secondary_and_tertiary_inverse_disabled_light = 17170562; // 0x1060082
+    field public static final int system_text_secondary_and_tertiary_inverse_light = 17170560; // 0x1060080
     field public static final int tab_indicator_text = 17170441; // 0x1060009
     field @Deprecated public static final int tertiary_text_dark = 17170448; // 0x1060010
     field @Deprecated public static final int tertiary_text_light = 17170449; // 0x1060011
@@ -2310,7 +2310,7 @@
     field public static final int accessibilityActionPageUp = 16908358; // 0x1020046
     field public static final int accessibilityActionPressAndHold = 16908362; // 0x102004a
     field public static final int accessibilityActionScrollDown = 16908346; // 0x102003a
-    field public static final int accessibilityActionScrollInDirection;
+    field public static final int accessibilityActionScrollInDirection = 16908382; // 0x102005e
     field public static final int accessibilityActionScrollLeft = 16908345; // 0x1020039
     field public static final int accessibilityActionScrollRight = 16908347; // 0x102003b
     field public static final int accessibilityActionScrollToPosition = 16908343; // 0x1020037
@@ -2331,7 +2331,7 @@
     field public static final int addToDictionary = 16908330; // 0x102002a
     field public static final int autofill = 16908355; // 0x1020043
     field public static final int background = 16908288; // 0x1020000
-    field public static final int bold;
+    field public static final int bold = 16908379; // 0x102005b
     field public static final int button1 = 16908313; // 0x1020019
     field public static final int button2 = 16908314; // 0x102001a
     field public static final int button3 = 16908315; // 0x102001b
@@ -2357,7 +2357,7 @@
     field public static final int inputExtractAccessories = 16908378; // 0x102005a
     field public static final int inputExtractAction = 16908377; // 0x1020059
     field public static final int inputExtractEditText = 16908325; // 0x1020025
-    field public static final int italic;
+    field public static final int italic = 16908380; // 0x102005c
     field @Deprecated public static final int keyboardView = 16908326; // 0x1020026
     field public static final int list = 16908298; // 0x102000a
     field public static final int list_container = 16908351; // 0x102003f
@@ -2389,7 +2389,7 @@
     field public static final int textAssist = 16908353; // 0x1020041
     field public static final int title = 16908310; // 0x1020016
     field public static final int toggle = 16908311; // 0x1020017
-    field public static final int underline;
+    field public static final int underline = 16908381; // 0x102005d
     field public static final int undo = 16908338; // 0x1020032
     field public static final int widget_frame = 16908312; // 0x1020018
   }
@@ -13662,7 +13662,6 @@
   }
 
   public final class CredentialOption implements android.os.Parcelable {
-    ctor @Deprecated public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
     method public int describeContents();
     method @NonNull public java.util.Set<android.content.ComponentName> getAllowedProviders();
     method @NonNull public android.os.Bundle getCandidateQueryData();
@@ -32653,7 +32652,7 @@
     field public static final int S = 31; // 0x1f
     field public static final int S_V2 = 32; // 0x20
     field public static final int TIRAMISU = 33; // 0x21
-    field public static final int UPSIDE_DOWN_CAKE = 10000; // 0x2710
+    field public static final int UPSIDE_DOWN_CAKE = 34; // 0x22
   }
 
   public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fbc69e3..ace7d59 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -410,14 +410,14 @@
     field public static final int sdkVersion = 16844304; // 0x1010610
     field public static final int supportsAmbientMode = 16844173; // 0x101058d
     field public static final int userRestriction = 16844164; // 0x1010584
-    field public static final int visualQueryDetectionService;
+    field public static final int visualQueryDetectionService = 16844410; // 0x101067a
   }
 
   public static final class R.bool {
-    field public static final int config_enableDefaultNotes;
-    field public static final int config_enableDefaultNotesForWorkProfile;
+    field public static final int config_enableDefaultNotes = 17891338; // 0x111000a
+    field public static final int config_enableDefaultNotesForWorkProfile = 17891339; // 0x111000b
     field public static final int config_enableQrCodeScannerOnLockScreen = 17891336; // 0x1110008
-    field public static final int config_safetyProtectionEnabled;
+    field public static final int config_safetyProtectionEnabled = 17891337; // 0x1110009
     field public static final int config_sendPackageName = 17891328; // 0x1110000
     field public static final int config_showDefaultAssistant = 17891329; // 0x1110001
     field public static final int config_showDefaultEmergency = 17891330; // 0x1110002
@@ -430,7 +430,7 @@
 
   public static final class R.dimen {
     field public static final int config_restrictedIconSize = 17104903; // 0x1050007
-    field public static final int config_viewConfigurationHandwritingGestureLineMargin;
+    field public static final int config_viewConfigurationHandwritingGestureLineMargin = 17104906; // 0x105000a
   }
 
   public static final class R.drawable {
@@ -452,7 +452,7 @@
     field public static final int config_defaultCallRedirection = 17039397; // 0x1040025
     field public static final int config_defaultCallScreening = 17039398; // 0x1040026
     field public static final int config_defaultDialer = 17039395; // 0x1040023
-    field public static final int config_defaultNotes;
+    field public static final int config_defaultNotes = 17039429; // 0x1040045
     field public static final int config_defaultSms = 17039396; // 0x1040024
     field public static final int config_devicePolicyManagement = 17039421; // 0x104003d
     field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f
@@ -468,10 +468,10 @@
     field public static final int config_systemAutomotiveCalendarSyncManager = 17039423; // 0x104003f
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
-    field public static final int config_systemCallStreaming;
+    field public static final int config_systemCallStreaming = 17039431; // 0x1040047
     field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039
     field public static final int config_systemContacts = 17039403; // 0x104002b
-    field public static final int config_systemFinancedDeviceController;
+    field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046
     field public static final int config_systemGallery = 17039399; // 0x1040027
     field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035
     field public static final int config_systemSettingsIntelligence = 17039426; // 0x1040042
@@ -483,7 +483,7 @@
     field public static final int config_systemUi = 17039418; // 0x104003a
     field public static final int config_systemUiIntelligence = 17039410; // 0x1040032
     field public static final int config_systemVisualIntelligence = 17039415; // 0x1040037
-    field public static final int config_systemWearHealthService;
+    field public static final int config_systemWearHealthService = 17039428; // 0x1040044
     field public static final int config_systemWellbeing = 17039408; // 0x1040030
     field public static final int config_systemWifiCoexManager = 17039407; // 0x104002f
     field public static final int safety_protection_display_text = 17039425; // 0x1040041
@@ -10117,7 +10117,7 @@
   public final class NetworkProviderInfo implements android.os.Parcelable {
     method public int describeContents();
     method @IntRange(from=0, to=100) public int getBatteryPercentage();
-    method @IntRange(from=0, to=3) public int getConnectionStrength();
+    method @IntRange(from=0, to=4) public int getConnectionStrength();
     method @NonNull public String getDeviceName();
     method public int getDeviceType();
     method @NonNull public android.os.Bundle getExtras();
@@ -10136,7 +10136,7 @@
     ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
-    method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=3) int);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceType(int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setExtras(@NonNull android.os.Bundle);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 2dfda51..2d9a99c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -8,8 +8,6 @@
     field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
     field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
     field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE";
-    field public static final String BODY_SENSORS_WRIST_TEMPERATURE = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE";
-    field public static final String BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND";
     field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
     field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
     field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
@@ -779,6 +777,17 @@
 
 }
 
+package android.app.tare {
+
+  public class EconomyManager {
+    method public int getEnabledMode();
+    field public static final int ENABLED_MODE_OFF = 0; // 0x0
+    field public static final int ENABLED_MODE_SHADOW = 2; // 0x2
+    field public static final String KEY_ENABLE_TARE_MODE = "enable_tare_mode";
+  }
+
+}
+
 package android.app.usage {
 
   public class StorageStatsManager {
@@ -1651,6 +1660,11 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.KeyphraseMetadata> CREATOR;
   }
 
+  public class SoundTrigger {
+    field public static final int MODEL_PARAM_INVALID = -1; // 0xffffffff
+    field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
+  }
+
   public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable {
     ctor public SoundTrigger.KeyphraseRecognitionExtra(int, int, int);
   }
@@ -1663,6 +1677,19 @@
     ctor public SoundTrigger.ModuleProperties(int, @NonNull String, @NonNull String, @NonNull String, int, @NonNull String, int, int, int, int, boolean, int, boolean, int, boolean, int);
   }
 
+  public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable {
+    ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int);
+    ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
+    field public final boolean allowMultipleTriggers;
+    field public final int audioCapabilities;
+    field public final boolean captureRequested;
+    field @NonNull public final byte[] data;
+    field @NonNull public final android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[] keyphrases;
+  }
+
   public static class SoundTrigger.RecognitionEvent {
     ctor public SoundTrigger.RecognitionEvent(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[], long);
   }
@@ -2000,6 +2027,57 @@
 
 }
 
+package android.media.soundtrigger {
+
+  public final class SoundTriggerInstrumentation {
+    method public void setResourceContention(boolean);
+    method public void triggerOnResourcesAvailable();
+    method public void triggerRestart();
+  }
+
+  public static interface SoundTriggerInstrumentation.GlobalCallback {
+    method public default void onClientAttached();
+    method public default void onClientDetached();
+    method public default void onFrameworkDetached();
+    method public void onModelLoaded(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelSession);
+    method public default void onPreempted();
+    method public default void onRestarted();
+  }
+
+  public static interface SoundTriggerInstrumentation.ModelCallback {
+    method public default void onModelUnloaded();
+    method public default void onParamSet(int, int);
+    method public void onRecognitionStarted(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionSession);
+  }
+
+  public class SoundTriggerInstrumentation.ModelSession {
+    method public void clearModelCallback();
+    method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.Keyphrase> getPhrases();
+    method @NonNull public android.media.soundtrigger.SoundTriggerManager.Model getSoundModel();
+    method public boolean isKeyphrase();
+    method public void setModelCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelCallback);
+    method public void triggerUnloadModel();
+  }
+
+  public static interface SoundTriggerInstrumentation.RecognitionCallback {
+    method public void onRecognitionStopped();
+  }
+
+  public class SoundTriggerInstrumentation.RecognitionSession {
+    method public void clearRecognitionCallback();
+    method public int getAudioSession();
+    method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig getRecognitionConfig();
+    method public void setRecognitionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionCallback);
+    method public void triggerAbortRecognition();
+    method public void triggerRecognitionEvent(@NonNull byte[], @Nullable java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+  }
+
+  public final class SoundTriggerManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public static android.media.soundtrigger.SoundTriggerInstrumentation attachInstrumentation(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.GlobalCallback);
+  }
+
+}
+
 package android.media.tv {
 
   public final class TvInputManager {
@@ -2024,6 +2102,14 @@
 
 }
 
+package android.media.voice {
+
+  public final class KeyphraseModelManager {
+    method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void setModelDatabaseForTestEnabled(boolean);
+  }
+
+}
+
 package android.net {
 
   public class NetworkPolicyManager {
@@ -2944,6 +3030,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @NonNull public final java.util.List<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> listModuleProperties();
+    method public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean);
   }
 
   public static class VoiceInteractionSession.ActivityId {
@@ -3351,8 +3438,14 @@
     method @NonNull public android.hardware.input.InputDeviceIdentifier getIdentifier();
   }
 
+  public abstract class InputEvent implements android.os.Parcelable {
+    method public abstract int getDisplayId();
+    method public abstract void setDisplayId(int);
+  }
+
   public class KeyEvent extends android.view.InputEvent implements android.os.Parcelable {
     method public static String actionToString(int);
+    method public final int getDisplayId();
     method public final void setDisplayId(int);
     field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
     field public static final int LAST_KEYCODE = 316; // 0x13c
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0293bb5..95e446d 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -343,7 +343,170 @@
      */
     public abstract boolean hasRunningActivity(int uid, @Nullable String packageName);
 
-    public abstract void updateOomAdj();
+    /**
+     * Oom Adj Reason: none - internal use only, do not use it.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_NONE = 0;
+
+    /**
+     * Oom Adj Reason: activity changes.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_ACTIVITY = 1;
+
+    /**
+     * Oom Adj Reason: finishing a broadcast receiver.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
+
+    /**
+     * Oom Adj Reason: starting a broadcast receiver.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_START_RECEIVER = 3;
+
+    /**
+     * Oom Adj Reason: binding to a service.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
+
+    /**
+     * Oom Adj Reason: unbinding from a service.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
+
+    /**
+     * Oom Adj Reason: starting a service.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_START_SERVICE = 6;
+
+    /**
+     * Oom Adj Reason: connecting to a content provider.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
+
+    /**
+     * Oom Adj Reason: disconnecting from a content provider.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
+
+    /**
+     * Oom Adj Reason: UI visibility changes.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
+
+    /**
+     * Oom Adj Reason: device power allowlist changes.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_ALLOWLIST = 10;
+
+    /**
+     * Oom Adj Reason: starting a process.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
+
+    /**
+     * Oom Adj Reason: ending a process.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_PROCESS_END = 12;
+
+    /**
+     * Oom Adj Reason: short FGS timeout.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
+
+    /**
+     * Oom Adj Reason: system initialization.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_SYSTEM_INIT = 14;
+
+    /**
+     * Oom Adj Reason: backup/restore.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_BACKUP = 15;
+
+    /**
+     * Oom Adj Reason: instrumented by the SHELL.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_SHELL = 16;
+
+    /**
+     * Oom Adj Reason: task stack is being removed.
+     */
+    public static final int OOM_ADJ_REASON_REMOVE_TASK = 17;
+
+    /**
+     * Oom Adj Reason: uid idle.
+     */
+    public static final int OOM_ADJ_REASON_UID_IDLE = 18;
+
+    /**
+     * Oom Adj Reason: stop service.
+     */
+    public static final int OOM_ADJ_REASON_STOP_SERVICE = 19;
+
+    /**
+     * Oom Adj Reason: executing service.
+     */
+    public static final int OOM_ADJ_REASON_EXECUTING_SERVICE = 20;
+
+    /**
+     * Oom Adj Reason: background restriction changes.
+     */
+    public static final int OOM_ADJ_REASON_RESTRICTION_CHANGE = 21;
+
+    /**
+     * Oom Adj Reason: A package or its component is disabled.
+     */
+    public static final int OOM_ADJ_REASON_COMPONENT_DISABLED = 22;
+
+    @IntDef(prefix = {"OOM_ADJ_REASON_"}, value = {
+        OOM_ADJ_REASON_NONE,
+        OOM_ADJ_REASON_ACTIVITY,
+        OOM_ADJ_REASON_FINISH_RECEIVER,
+        OOM_ADJ_REASON_START_RECEIVER,
+        OOM_ADJ_REASON_BIND_SERVICE,
+        OOM_ADJ_REASON_UNBIND_SERVICE,
+        OOM_ADJ_REASON_START_SERVICE,
+        OOM_ADJ_REASON_GET_PROVIDER,
+        OOM_ADJ_REASON_REMOVE_PROVIDER,
+        OOM_ADJ_REASON_UI_VISIBILITY,
+        OOM_ADJ_REASON_ALLOWLIST,
+        OOM_ADJ_REASON_PROCESS_BEGIN,
+        OOM_ADJ_REASON_PROCESS_END,
+        OOM_ADJ_REASON_SHORT_FGS_TIMEOUT,
+        OOM_ADJ_REASON_SYSTEM_INIT,
+        OOM_ADJ_REASON_BACKUP,
+        OOM_ADJ_REASON_SHELL,
+        OOM_ADJ_REASON_REMOVE_TASK,
+        OOM_ADJ_REASON_UID_IDLE,
+        OOM_ADJ_REASON_STOP_SERVICE,
+        OOM_ADJ_REASON_EXECUTING_SERVICE,
+        OOM_ADJ_REASON_RESTRICTION_CHANGE,
+        OOM_ADJ_REASON_COMPONENT_DISABLED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface OomAdjReason {}
+
+    /**
+     * Request to update oom adj.
+     */
+    public abstract void updateOomAdj(@OomAdjReason int oomAdjReason);
     public abstract void updateCpuStats();
 
     /**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b48a8fb..3312294 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1450,9 +1450,8 @@
     public static final int OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD =
             AppProtoEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
 
-    /** @hide Access to wrist temperature sensors. */
-    public static final int OP_BODY_SENSORS_WRIST_TEMPERATURE =
-            AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
+    // App op deprecated/removed.
+    private static final int OP_DEPRECATED_2 = AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
 
     /**
      * Send an intent to launch instead of posting the notification to the status bar.
@@ -1461,9 +1460,25 @@
      */
     public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT;
 
+    /**
+     * Hides camera indicator for sandboxed detection apps that directly access the service.
+     *
+     * @hide
+     */
+    public static final int OP_CAMERA_SANDBOXED =
+            AppProtoEnums.APP_OP_CAMERA_SANDBOXED;
+
+    /**
+     * Hides microphone indicator for sandboxed detection apps that directly access the service.
+     *
+     * @hide
+     */
+    public static final int OP_RECORD_AUDIO_SANDBOXED =
+            AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 134;
+    public static final int _NUM_OP = 136;
 
     /**
      * All app ops represented as strings.
@@ -1603,8 +1618,9 @@
             OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION,
             OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
             OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
-            OPSTR_BODY_SENSORS_WRIST_TEMPERATURE,
             OPSTR_USE_FULL_SCREEN_INTENT,
+            OPSTR_CAMERA_SANDBOXED,
+            OPSTR_RECORD_AUDIO_SANDBOXED
     })
     public @interface AppOpString {}
 
@@ -2013,6 +2029,20 @@
     public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source";
 
     /**
+     * Camera is being recorded in sandboxed detection process.
+     *
+     * @hide
+     */
+    public static final String OPSTR_CAMERA_SANDBOXED = "android:camera_sandboxed";
+
+    /**
+     * Audio is being recorded in sandboxed detection process.
+     *
+     * @hide
+     */
+    public static final String OPSTR_RECORD_AUDIO_SANDBOXED = "android:record_audio_sandboxed";
+
+    /**
      * Allow apps to create the requests to manage the media files without user confirmation.
      *
      * @see android.Manifest.permission#MANAGE_MEDIA
@@ -2189,11 +2219,10 @@
             "android:capture_consentless_bugreport_on_userdebug_build";
 
     /**
-     * Access to wrist temperature body sensors.
+     * App op deprecated/removed.
      * @hide
      */
-    public static final String OPSTR_BODY_SENSORS_WRIST_TEMPERATURE =
-            "android:body_sensors_wrist_temperature";
+    public static final String OPSTR_DEPRECATED_2 = "android:deprecated_2";
 
     /**
      * Send an intent to launch instead of posting the notification to the status bar.
@@ -2311,7 +2340,6 @@
             OP_READ_MEDIA_VISUAL_USER_SELECTED,
             OP_FOREGROUND_SERVICE_SPECIAL_USE,
             OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
-            OP_BODY_SENSORS_WRIST_TEMPERATURE,
             OP_USE_FULL_SCREEN_INTENT
     };
 
@@ -2731,14 +2759,15 @@
                 "CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD")
                 .setPermission(Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD)
                 .build(),
-        new AppOpInfo.Builder(OP_BODY_SENSORS_WRIST_TEMPERATURE,
-                OPSTR_BODY_SENSORS_WRIST_TEMPERATURE,
-                "BODY_SENSORS_WRIST_TEMPERATURE")
-                .setPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE)
-                .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_DEPRECATED_2, OPSTR_DEPRECATED_2, "DEPRECATED_2")
+                .setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
         new AppOpInfo.Builder(OP_USE_FULL_SCREEN_INTENT, OPSTR_USE_FULL_SCREEN_INTENT,
                 "USE_FULL_SCREEN_INTENT").setPermission(Manifest.permission.USE_FULL_SCREEN_INTENT)
-                .build()
+                .build(),
+        new AppOpInfo.Builder(OP_CAMERA_SANDBOXED, OPSTR_CAMERA_SANDBOXED,
+            "CAMERA_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RECORD_AUDIO_SANDBOXED, OPSTR_RECORD_AUDIO_SANDBOXED,
+                "RECORD_AUDIO_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index be012cf..c0c59a2 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -473,7 +473,6 @@
             new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
                 new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION),
                 new RegularPermission(Manifest.permission.BODY_SENSORS),
-                new RegularPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE),
                 new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS),
             }, false),
             FGS_TYPE_PERM_ENFORCEMENT_FLAG_HEALTH /* permissionEnforcementFlag */,
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index ee24263..2b15589 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -220,6 +220,20 @@
     void notifyGoingToSleep(int x, int y, in Bundle extras);
 
     /**
+     * Called when the screen has been fully turned on and is visible.
+     *
+     * @hide
+     */
+    void notifyScreenTurnedOn(int displayId);
+
+    /**
+     * Called when the screen starts turning on.
+     *
+     * @hide
+     */
+    void notifyScreenTurningOn(int displayId);
+
+    /**
      * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default
      * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black.
      *
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index c131ce5..e31486f 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -2354,8 +2354,7 @@
                 return mUiAutomation;
             }
             if (mustCreateNewAutomation) {
-                mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(),
-                        mUiAutomationConnection);
+                mUiAutomation = new UiAutomation(getTargetContext(), mUiAutomationConnection);
             } else {
                 mUiAutomation.disconnect();
             }
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 1df8602..bc5f7f4 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -110,6 +110,9 @@
             "options": [
                 {
                     "exclude-annotation": "androidx.test.filters.FlakyTest"
+                },
+                {
+                    "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceStressTest"
                 }
             ],
             "file_patterns": ["(/|^)VoiceInteract[^/]*"]
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 658e084..247d5bc 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityService.Callbacks;
@@ -30,6 +32,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -45,6 +48,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.ArraySet;
 import android.util.DebugUtils;
 import android.util.Log;
@@ -69,8 +73,10 @@
 import android.view.inputmethod.EditorInfo;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
 import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import libcore.io.IoUtils;
@@ -202,6 +208,8 @@
 
     private final IUiAutomationConnection mUiAutomationConnection;
 
+    private final int mDisplayId;
+
     private HandlerThread mRemoteCallbackThread;
 
     private IAccessibilityServiceClient mClient;
@@ -261,24 +269,49 @@
 
     /**
      * Creates a new instance that will handle callbacks from the accessibility
+     * layer on the thread of the provided context main looper and perform requests for privileged
+     * operations on the provided connection, and filtering display-related features to the display
+     * associated with the context (or the user running the test, on devices that
+     * {@link UserManager#isVisibleBackgroundUsersSupported() support visible background users}).
+     *
+     * @param context the context associated with the automation
+     * @param connection The connection for performing privileged operations.
+     *
+     * @hide
+     */
+    public UiAutomation(Context context, IUiAutomationConnection connection) {
+        this(getDisplayId(context), context.getMainLooper(), connection);
+    }
+
+    /**
+     * Creates a new instance that will handle callbacks from the accessibility
      * layer on the thread of the provided looper and perform requests for privileged
      * operations on the provided connection.
      *
      * @param looper The looper on which to execute accessibility callbacks.
      * @param connection The connection for performing privileged operations.
      *
+     * @deprecated use {@link #UiAutomation(Context, IUiAutomationConnection)} instead
+     *
      * @hide
      */
+    @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public UiAutomation(Looper looper, IUiAutomationConnection connection) {
-        if (looper == null) {
-            throw new IllegalArgumentException("Looper cannot be null!");
-        }
-        if (connection == null) {
-            throw new IllegalArgumentException("Connection cannot be null!");
-        }
+        this(DEFAULT_DISPLAY, looper, connection);
+        Log.w(LOG_TAG, "Created with deprecatead constructor, assumes DEFAULT_DISPLAY");
+    }
+
+    private UiAutomation(int displayId, Looper looper, IUiAutomationConnection connection) {
+        Preconditions.checkArgument(looper != null, "Looper cannot be null!");
+        Preconditions.checkArgument(connection != null, "Connection cannot be null!");
+
         mLocalCallbackHandler = new Handler(looper);
         mUiAutomationConnection = connection;
+        mDisplayId = displayId;
+
+        Log.i(LOG_TAG, "Initialized for user " + Process.myUserHandle().getIdentifier()
+                + " on display " + mDisplayId);
     }
 
     /**
@@ -719,8 +752,14 @@
     }
 
     /**
-     * Gets the windows on the screen of the default display. This method returns only the windows
-     * that a sighted user can interact with, as opposed to all windows.
+     * Gets the windows on the screen associated with the {@link UiAutomation} context (usually the
+     * {@link android.view.Display#DEFAULT_DISPLAY default display).
+     *
+     * <p>
+     * This method returns only the windows that a sighted user can interact with, as opposed to
+     * all windows.
+
+     * <p>
      * For example, if there is a modal dialog shown and the user cannot touch
      * anything behind it, then only the modal window will be reported
      * (assuming it is the top one). For convenience the returned windows
@@ -730,21 +769,23 @@
      * <strong>Note:</strong> In order to access the windows you have to opt-in
      * to retrieve the interactive windows by setting the
      * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
-     * </p>
      *
      * @return The windows if there are windows such, otherwise an empty list.
      * @throws IllegalStateException If the connection to the accessibility subsystem is not
      *            established.
      */
     public List<AccessibilityWindowInfo> getWindows() {
+        if (DEBUG) {
+            Log.d(LOG_TAG, "getWindows(): returning windows for display " + mDisplayId);
+        }
         final int connectionId;
         synchronized (mLock) {
             throwIfNotConnectedLocked();
             connectionId = mConnectionId;
         }
         // Calling out without a lock held.
-        return AccessibilityInteractionClient.getInstance()
-                .getWindows(connectionId);
+        return AccessibilityInteractionClient.getInstance().getWindowsOnDisplay(connectionId,
+                mDisplayId);
     }
 
     /**
@@ -1112,8 +1153,10 @@
      * @return The screenshot bitmap on success, null otherwise.
      */
     public Bitmap takeScreenshot() {
-        Display display = DisplayManagerGlobal.getInstance()
-                .getRealDisplay(Display.DEFAULT_DISPLAY);
+        if (DEBUG) {
+            Log.d(LOG_TAG, "Taking screenshot of display " + mDisplayId);
+        }
+        Display display = DisplayManagerGlobal.getInstance().getRealDisplay(mDisplayId);
         Point displaySize = new Point();
         display.getRealSize(displaySize);
 
@@ -1126,10 +1169,12 @@
             screenShot = mUiAutomationConnection.takeScreenshot(
                     new Rect(0, 0, displaySize.x, displaySize.y));
             if (screenShot == null) {
+                Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display "
+                        + mDisplayId);
                 return null;
             }
         } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while taking screenshot!", re);
+            Log.e(LOG_TAG, "Error while taking screenshot of display " + mDisplayId, re);
             return null;
         }
 
@@ -1509,6 +1554,14 @@
         return executeShellCommandInternal(command, true /* includeStderr */);
     }
 
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
     private ParcelFileDescriptor[] executeShellCommandInternal(
             String command, boolean includeStderr) {
         warnIfBetterCommand(command);
@@ -1564,6 +1617,7 @@
         final StringBuilder stringBuilder = new StringBuilder();
         stringBuilder.append("UiAutomation@").append(Integer.toHexString(hashCode()));
         stringBuilder.append("[id=").append(mConnectionId);
+        stringBuilder.append(", displayId=").append(mDisplayId);
         stringBuilder.append(", flags=").append(mFlags);
         stringBuilder.append("]");
         return stringBuilder.toString();
@@ -1601,6 +1655,55 @@
         return (mFlags & UiAutomation.FLAG_DONT_USE_ACCESSIBILITY) == 0;
     }
 
+    /**
+     * Gets the display id associated with the UiAutomation context.
+     *
+     * <p><b>NOTE: </b> must be a static method because it's called from a constructor to call
+     * another one.
+     */
+    private static int getDisplayId(Context context) {
+        Preconditions.checkArgument(context != null, "Context cannot be null!");
+
+        UserManager userManager = context.getSystemService(UserManager.class);
+        // TODO(b/255426725): given that this is a temporary solution until a11y supports multiple
+        // users, the display is only set on devices that support that
+        if (!userManager.isVisibleBackgroundUsersSupported()) {
+            return DEFAULT_DISPLAY;
+        }
+
+        int displayId = context.getDisplayId();
+        if (displayId == Display.INVALID_DISPLAY) {
+            // Shouldn't happen, but we better handle it
+            Log.e(LOG_TAG, "UiAutomation created UI context with invalid display id, assuming it's"
+                    + " running in the display assigned to the user");
+            return getMainDisplayIdAssignedToUser(context, userManager);
+        }
+
+        if (displayId != DEFAULT_DISPLAY) {
+            if (DEBUG) {
+                Log.d(LOG_TAG, "getDisplayId(): returning context's display (" + displayId + ")");
+            }
+            // Context is explicitly setting the display, so we respect that...
+            return displayId;
+        }
+        // ...otherwise, we need to get the display the test's user is running on
+        int userDisplayId = getMainDisplayIdAssignedToUser(context, userManager);
+        if (DEBUG) {
+            Log.d(LOG_TAG, "getDisplayId(): returning user's display (" + userDisplayId + ")");
+        }
+        return userDisplayId;
+    }
+
+    private static int getMainDisplayIdAssignedToUser(Context context, UserManager userManager) {
+        if (!userManager.isUserVisible()) {
+            // Should also not happen, but ...
+            Log.e(LOG_TAG, "User (" + context.getUserId() + ") is not visible, using "
+                    + "DEFAULT_DISPLAY");
+            return DEFAULT_DISPLAY;
+        }
+        return userManager.getMainDisplayIdAssignedToUser();
+    }
+
     private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
 
         public IAccessibilityServiceClientImpl(Looper looper, int generationId) {
@@ -1621,6 +1724,7 @@
                     if (DEBUG) {
                         Log.d(LOG_TAG, "init(): connectionId=" + connectionId + ", windowToken="
                                 + windowToken + ", user=" + Process.myUserHandle()
+                                + ", UiAutomation.mDisplay=" + UiAutomation.this.mDisplayId
                                 + ", mGenerationId=" + mGenerationId
                                 + ", UiAutomation.mGenerationId="
                                 + UiAutomation.this.mGenerationId);
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 13e800e..d96a9d1 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -22,6 +22,7 @@
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -117,7 +118,8 @@
                 throw new IllegalStateException("Already connected.");
             }
             mOwningUid = Binder.getCallingUid();
-            registerUiTestAutomationServiceLocked(client, flags);
+            registerUiTestAutomationServiceLocked(client,
+                    Binder.getCallingUserHandle().getIdentifier(), flags);
             storeRotationStateLocked();
         }
     }
@@ -553,7 +555,7 @@
     }
 
     private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client,
-            int flags) {
+            @UserIdInt int userId, int flags) {
         IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
                 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
         final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
@@ -571,10 +573,11 @@
         try {
             // Calling out with a lock held is fine since if the system
             // process is gone the client calling in will be killed.
-            manager.registerUiTestAutomationService(mToken, client, info, flags);
+            manager.registerUiTestAutomationService(mToken, client, info, userId, flags);
             mClient = client;
         } catch (RemoteException re) {
-            throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
+            throw new IllegalStateException("Error while registering UiTestAutomationService for "
+                    + "user " + userId + ".", re);
         }
     }
 
diff --git a/core/java/android/credentials/CredentialOption.java b/core/java/android/credentials/CredentialOption.java
index e933123..df948f17 100644
--- a/core/java/android/credentials/CredentialOption.java
+++ b/core/java/android/credentials/CredentialOption.java
@@ -37,8 +37,7 @@
 
 /**
  * Information about a specific type of credential to be requested during a {@link
- * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor,
- * OutcomeReceiver)} operation.
+ * CredentialManager#getCredential} operation.
  */
 public final class CredentialOption implements Parcelable {
 
@@ -196,9 +195,8 @@
      * @throws NullPointerException If {@code credentialRetrievalData}, or
      * {@code candidateQueryData} is null.
      *
-     * @deprecated replaced by Builder
+     * @hide
      */
-    @Deprecated
     public CredentialOption(
             @NonNull String type,
             @NonNull Bundle credentialRetrievalData,
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java
index 852934a..629d578 100644
--- a/core/java/android/credentials/ui/CreateCredentialProviderData.java
+++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
+import android.content.pm.ParceledListSlice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -35,7 +36,7 @@
 @TestApi
 public final class CreateCredentialProviderData extends ProviderData implements Parcelable {
     @NonNull
-    private final List<Entry> mSaveEntries;
+    private final ParceledListSlice<Entry> mSaveEntries;
     @Nullable
     private final Entry mRemoteEntry;
 
@@ -43,13 +44,13 @@
             @NonNull String providerFlattenedComponentName, @NonNull List<Entry> saveEntries,
             @Nullable Entry remoteEntry) {
         super(providerFlattenedComponentName);
-        mSaveEntries = saveEntries;
+        mSaveEntries = new ParceledListSlice<>(saveEntries);
         mRemoteEntry = remoteEntry;
     }
 
     @NonNull
     public List<Entry> getSaveEntries() {
-        return mSaveEntries;
+        return mSaveEntries.getList();
     }
 
     @Nullable
@@ -60,9 +61,7 @@
     private CreateCredentialProviderData(@NonNull Parcel in) {
         super(in);
 
-        List<Entry> credentialEntries = new ArrayList<>();
-        in.readTypedList(credentialEntries, Entry.CREATOR);
-        mSaveEntries = credentialEntries;
+        mSaveEntries = in.readParcelable(null, android.content.pm.ParceledListSlice.class);
         AnnotationValidations.validate(NonNull.class, null, mSaveEntries);
 
         Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
@@ -72,7 +71,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
-        dest.writeTypedList(mSaveEntries);
+        dest.writeParcelable(mSaveEntries, flags);
         dest.writeTypedObject(mRemoteEntry, flags);
     }
 
diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java
index e4688a84..773dee9 100644
--- a/core/java/android/credentials/ui/GetCredentialProviderData.java
+++ b/core/java/android/credentials/ui/GetCredentialProviderData.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
+import android.content.pm.ParceledListSlice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -35,11 +36,11 @@
 @TestApi
 public final class GetCredentialProviderData extends ProviderData implements Parcelable {
     @NonNull
-    private final List<Entry> mCredentialEntries;
+    private final ParceledListSlice<Entry> mCredentialEntries;
     @NonNull
-    private final List<Entry> mActionChips;
+    private final ParceledListSlice<Entry> mActionChips;
     @NonNull
-    private final List<AuthenticationEntry> mAuthenticationEntries;
+    private final ParceledListSlice<AuthenticationEntry> mAuthenticationEntries;
     @Nullable
     private final Entry mRemoteEntry;
 
@@ -49,25 +50,25 @@
             @NonNull List<AuthenticationEntry> authenticationEntries,
             @Nullable Entry remoteEntry) {
         super(providerFlattenedComponentName);
-        mCredentialEntries = credentialEntries;
-        mActionChips = actionChips;
-        mAuthenticationEntries = authenticationEntries;
+        mCredentialEntries = new ParceledListSlice<>(credentialEntries);
+        mActionChips = new ParceledListSlice<>(actionChips);
+        mAuthenticationEntries = new ParceledListSlice<>(authenticationEntries);
         mRemoteEntry = remoteEntry;
     }
 
     @NonNull
     public List<Entry> getCredentialEntries() {
-        return mCredentialEntries;
+        return mCredentialEntries.getList();
     }
 
     @NonNull
     public List<Entry> getActionChips() {
-        return mActionChips;
+        return mActionChips.getList();
     }
 
     @NonNull
     public List<AuthenticationEntry> getAuthenticationEntries() {
-        return mAuthenticationEntries;
+        return mAuthenticationEntries.getList();
     }
 
     @Nullable
@@ -77,20 +78,16 @@
 
     private GetCredentialProviderData(@NonNull Parcel in) {
         super(in);
-
-        List<Entry> credentialEntries = new ArrayList<>();
-        in.readTypedList(credentialEntries, Entry.CREATOR);
-        mCredentialEntries = credentialEntries;
+        mCredentialEntries = in.readParcelable(null,
+                android.content.pm.ParceledListSlice.class);
         AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
 
-        List<Entry> actionChips  = new ArrayList<>();
-        in.readTypedList(actionChips, Entry.CREATOR);
-        mActionChips = actionChips;
+        mActionChips = in.readParcelable(null,
+                android.content.pm.ParceledListSlice.class);
         AnnotationValidations.validate(NonNull.class, null, mActionChips);
 
-        List<AuthenticationEntry> authenticationEntries  = new ArrayList<>();
-        in.readTypedList(authenticationEntries, AuthenticationEntry.CREATOR);
-        mAuthenticationEntries = authenticationEntries;
+        mAuthenticationEntries = in.readParcelable(null,
+                android.content.pm.ParceledListSlice.class);
         AnnotationValidations.validate(NonNull.class, null, mAuthenticationEntries);
 
         Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
@@ -100,9 +97,9 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
-        dest.writeTypedList(mCredentialEntries);
-        dest.writeTypedList(mActionChips);
-        dest.writeTypedList(mAuthenticationEntries);
+        dest.writeParcelable(mCredentialEntries, flags);
+        dest.writeParcelable(mActionChips, flags);
+        dest.writeParcelable(mAuthenticationEntries, flags);
         dest.writeTypedObject(mRemoteEntry, flags);
     }
 
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index fa678fc..2e40f60 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -142,6 +142,7 @@
         private PromptInfo mPromptInfo;
         private ButtonInfo mNegativeButtonInfo;
         private Context mContext;
+        private IAuthService mService;
 
         /**
          * Creates a builder for a {@link BiometricPrompt} dialog.
@@ -212,6 +213,18 @@
         }
 
         /**
+         * @param service
+         * @return This builder.
+         * @hide
+         */
+        @RequiresPermission(TEST_BIOMETRIC)
+        @NonNull
+        public Builder setService(@NonNull IAuthService service) {
+            mService = service;
+            return this;
+        }
+
+        /**
          * Sets an optional title, subtitle, and/or description that will override other text when
          * the user is authenticating with PIN/pattern/password. Currently for internal use only.
          * @return This builder.
@@ -472,7 +485,9 @@
                 throw new IllegalArgumentException("Can't have both negative button behavior"
                         + " and device credential enabled");
             }
-            return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo);
+            mService = (mService == null) ? IAuthService.Stub.asInterface(
+                    ServiceManager.getService(Context.AUTH_SERVICE)) : mService;
+            return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo, mService);
         }
     }
 
@@ -521,7 +536,6 @@
         public void onAuthenticationFailed() {
             mExecutor.execute(() -> {
                 mAuthenticationCallback.onAuthenticationFailed();
-                mIsPromptShowing = false;
             });
         }
 
@@ -604,12 +618,12 @@
 
     private boolean mIsPromptShowing;
 
-    private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo) {
+    private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo,
+            IAuthService service) {
         mContext = context;
         mPromptInfo = promptInfo;
         mNegativeButtonInfo = negativeButtonInfo;
-        mService = IAuthService.Stub.asInterface(
-                ServiceManager.getService(Context.AUTH_SERVICE));
+        mService = service;
         mIsPromptShowing = false;
     }
 
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 5feda78..ad68866 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -1310,6 +1310,10 @@
      * {@link Surface}, submitting a reprocess {@link CaptureRequest} with multiple
      * output targets will result in a {@link CaptureFailure}.
      *
+     * From Android 14 onward, {@link CaptureRequest#CONTROL_CAPTURE_INTENT} will be set to
+     * {@link CameraMetadata#CONTROL_CAPTURE_INTENT_STILL_CAPTURE} by default. Prior to Android 14,
+     * apps will need to explicitly set this key themselves.
+     *
      * @param inputResult The capture result of the output image or one of the output images used
      *                       to generate the reprocess input image for this capture request.
      *
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index cb1efe8..f2d8caa 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -26,6 +26,7 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.CameraOfflineSession;
 import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
@@ -861,8 +862,13 @@
             CameraMetadataNative resultMetadata = new
                     CameraMetadataNative(inputResult.getNativeCopy());
 
-            return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true,
-                    inputResult.getSessionId(), getId(), /*physicalCameraIdSet*/ null);
+            CaptureRequest.Builder builder = new CaptureRequest.Builder(resultMetadata,
+                    /*reprocess*/true, inputResult.getSessionId(), getId(),
+                    /*physicalCameraIdSet*/ null);
+            builder.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
+                    CameraMetadata.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
+
+            return builder;
         }
     }
 
diff --git a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
index dcc3369..4663730 100644
--- a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
+++ b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
@@ -42,12 +42,6 @@
     void onGenericSoundTriggerDetected(in SoundTrigger.GenericRecognitionEvent recognitionEvent);
 
     /**
-     * Called when the detection fails due to an error.
-     *
-     * @param status The error code that was seen.
-     */
-    void onError(int status);
-    /**
      * Called when the recognition is paused temporarily for some reason.
      */
     void onRecognitionPaused();
@@ -55,4 +49,28 @@
      * Called when the recognition is resumed after it was temporarily paused.
      */
     void onRecognitionResumed();
+
+    // Error callbacks to follow
+    /**
+     * Called when this recognition has been preempted by another.
+     */
+    void onPreempted();
+
+    /**
+     * Called when the underlying ST module service has died.
+     */
+    void onModuleDied();
+
+    /**
+     * Called when the service failed to gracefully resume recognition following a pause.
+     * @param status - The received error code.
+     */
+    void onResumeFailed(int status);
+
+    /**
+     * Called when the service failed to pause recognition when required.
+     * TODO(b/276507281) Remove. This should never happen, so we should abort instead.
+     * @param status - The received error code.
+     */
+    void onPauseFailed(int status);
 }
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index fa16e16..6d43ddf 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1051,6 +1051,29 @@
             return "ModelParamRange [start=" + mStart + ", end=" + mEnd + "]";
         }
     }
+    /**
+     * SoundTrigger model parameter types.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "MODEL_PARAM" }, value = {
+            MODEL_PARAM_INVALID,
+            MODEL_PARAM_THRESHOLD_FACTOR
+    })
+    public @interface ModelParamTypes {}
+
+    /**
+     * See {@link ModelParams.INVALID}
+     * @hide
+     */
+    @TestApi
+    public static final int MODEL_PARAM_INVALID = ModelParams.INVALID;
+    /**
+     * See {@link ModelParams.THRESHOLD_FACTOR}
+     * @hide
+     */
+    @TestApi
+    public static final int MODEL_PARAM_THRESHOLD_FACTOR = ModelParams.THRESHOLD_FACTOR;
 
     /**
      * Modes for key phrase recognition
@@ -1450,7 +1473,8 @@
      *
      *  @hide
      */
-    public static class RecognitionConfig implements Parcelable {
+    @TestApi
+    public static final class RecognitionConfig implements Parcelable {
         /** True if the DSP should capture the trigger sound and make it available for further
          * capture. */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1464,6 +1488,7 @@
          * options for each keyphrase. */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         @NonNull
+        @SuppressLint("ArrayReturn")
         public final KeyphraseRecognitionExtra keyphrases[];
         /** Opaque data for use by system applications who know about voice engine internals,
          * typically during enrollment. */
@@ -1479,8 +1504,8 @@
         public final int audioCapabilities;
 
         public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
-                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
-                int audioCapabilities) {
+                @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
+                @Nullable byte[] data, int audioCapabilities) {
             this.captureRequested = captureRequested;
             this.allowMultipleTriggers = allowMultipleTriggers;
             this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
@@ -1490,7 +1515,8 @@
 
         @UnsupportedAppUsage
         public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
-                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+                @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
+                @Nullable byte[] data) {
             this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
         }
 
@@ -1517,7 +1543,7 @@
         }
 
         @Override
-        public void writeToParcel(Parcel dest, int flags) {
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeByte((byte) (captureRequested ? 1 : 0));
             dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
             dest.writeTypedArray(keyphrases, flags);
diff --git a/core/java/android/nfc/tech/NdefFormatable.java b/core/java/android/nfc/tech/NdefFormatable.java
index f19d302..2240fe7 100644
--- a/core/java/android/nfc/tech/NdefFormatable.java
+++ b/core/java/android/nfc/tech/NdefFormatable.java
@@ -124,6 +124,9 @@
         try {
             int serviceHandle = mTag.getServiceHandle();
             INfcTag tagService = mTag.getTagService();
+            if (tagService == null) {
+                throw new IOException();
+            }
             int errorCode = tagService.formatNdef(serviceHandle, MifareClassic.KEY_DEFAULT);
             switch (errorCode) {
                 case ErrorCodes.SUCCESS:
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 244632a..7383e63 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -493,7 +493,7 @@
          * @hide
          */
         @TestApi
-        public static final int RESOURCES_SDK_INT = SDK_INT + ACTIVE_CODENAMES.length;
+        public static final int RESOURCES_SDK_INT = SDK_INT;
 
         /**
          * The current lowest supported value of app target SDK. Applications targeting
@@ -1222,7 +1222,7 @@
         /**
          * Upside Down Cake.
          */
-        public static final int UPSIDE_DOWN_CAKE = CUR_DEVELOPMENT;
+        public static final int UPSIDE_DOWN_CAKE = 34;
     }
 
     /** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 63259ed..ecce2ff 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -202,6 +202,13 @@
     public static final String ACTION_NOTIFY_IF_IN_USE =
             "android.os.image.action.NOTIFY_IF_IN_USE";
 
+    /**
+     * Intent action: hide notifications about the status of {@code DynamicSystem}.
+     * @hide
+     */
+    public static final String ACTION_HIDE_NOTIFICATION =
+            "android.os.image.action.HIDE_NOTIFICATION";
+
     /*
      * Intent Keys
      */
@@ -217,6 +224,19 @@
      */
     public static final String KEY_USERDATA_SIZE = "KEY_USERDATA_SIZE";
 
+    /**
+     * Intent key: Whether to enable DynamicSystem immediately after installation is done.
+     *             Note this will reboot the device automatically.
+     * @hide
+     */
+    public static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
+
+    /**
+     * Intent key: Whether to leave DynamicSystem on device reboot.
+     *             False indicates a sticky mode where device stays in DynamicSystem across reboots.
+     * @hide
+     */
+    public static final String KEY_ONE_SHOT = "KEY_ONE_SHOT";
 
     private static class IncomingHandler extends Handler {
         private final WeakReference<DynamicSystemClient> mWeakClient;
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index d34b45b..4603e43f 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,18 +1,19 @@
 # Bug component: 137825
 
-evanseverson@google.com
-evanxinchen@google.com
 ashfall@google.com
-guojing@google.com
+augale@google.com
+evanseverson@google.com
+fayey@google.com
 jaysullivan@google.com
+joecastro@google.com
 kvakil@google.com
 mrulhania@google.com
 narayan@google.com
 ntmyren@google.com
 olekarg@google.com
 pyuli@google.com
-raphk@google.com
 rmacgregor@google.com
 sergeynv@google.com
 theianchen@google.com
+yutingfang@google.com
 zhanghai@google.com
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index d2f9ff0..59b945c 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -2051,6 +2051,14 @@
          * <P>Type: TEXT</P>
          */
         public static final String ADDRESS = "address";
+
+        /**
+         * The subscription to which the message belongs to. Its value will be less than 0
+         * if the sub id cannot be determined.
+         * <p>Type: INTEGER (long) </p>
+         * @hide
+         */
+        public static final String SUBSCRIPTION_ID = "sub_id";
     }
 
     /**
@@ -2119,6 +2127,14 @@
          * <P>Type: INTEGER (boolean)</P>
          */
         public static final String ARCHIVED = "archived";
+
+        /**
+         * The subscription to which the message belongs to. Its value will be less than 0
+         * if the sub id cannot be determined.
+         * <p>Type: INTEGER (long) </p>
+         * @hide
+         */
+        public static final String SUBSCRIPTION_ID = "sub_id";
     }
 
     /**
@@ -2477,6 +2493,14 @@
             public static final String CHARSET = "charset";
 
             /**
+             * The subscription to which the message belongs to. Its value will be less than 0
+             * if the sub id cannot be determined.
+             * <p>Type: INTEGER (long) </p>
+             * @hide
+             */
+            public static final String SUBSCRIPTION_ID = "sub_id";
+
+            /**
              * Generates a Addr {@link Uri} for message, used to perform Addr table operation
              * for mms.
              *
@@ -2597,6 +2621,14 @@
             public static final String TEXT = "text";
 
             /**
+             * The subscription to which the message belongs to. Its value will be less than 0
+             * if the sub id cannot be determined.
+             * <p>Type: INTEGER (long) </p>
+             * @hide
+             */
+            public static final String SUBSCRIPTION_ID = "sub_id";
+
+            /**
              * Generates a Part {@link Uri} for message, used to perform Part table operation
              * for mms.
              *
@@ -2635,6 +2667,14 @@
              * <P>Type: INTEGER (long)</P>
              */
             public static final String SENT_TIME = "sent_time";
+
+            /**
+             * The subscription to which the message belongs to. Its value will be less than 0
+             * if the sub id cannot be determined.
+             * <p>Type: INTEGER (long) </p>
+             * @hide
+             */
+            public static final String SUBSCRIPTION_ID = "sub_id";
         }
 
         /**
@@ -2868,6 +2908,14 @@
              * <P>Type: TEXT</P>
              */
             public static final String INDEXED_TEXT = "index_text";
+
+            /**
+             * The subscription to which the message belongs to. Its value will be less than 0
+             * if the sub id cannot be determined.
+             * <p>Type: INTEGER (long) </p>
+             * @hide
+             */
+            public static final String SUBSCRIPTION_ID = "sub_id";
         }
     }
 
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
index cd53cb6..df93433 100644
--- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.content.pm.ParceledListSlice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -33,7 +34,7 @@
  * Response to a {@link BeginCreateCredentialRequest}.
  */
 public final class BeginCreateCredentialResponse implements Parcelable {
-    private final @NonNull List<CreateEntry> mCreateEntries;
+    private final @NonNull ParceledListSlice<CreateEntry> mCreateEntries;
     private final @Nullable RemoteEntry mRemoteCreateEntry;
 
     /**
@@ -41,19 +42,19 @@
      * to return.
      */
     public BeginCreateCredentialResponse() {
-        this(/*createEntries=*/new ArrayList<>(), /*remoteCreateEntry=*/null);
+        this(/*createEntries=*/new ParceledListSlice<>(new ArrayList<>()),
+                /*remoteCreateEntry=*/null);
     }
 
     private BeginCreateCredentialResponse(@NonNull Parcel in) {
-        List<CreateEntry> createEntries = new ArrayList<>();
-        in.readTypedList(createEntries, CreateEntry.CREATOR);
-        mCreateEntries = createEntries;
+        mCreateEntries = in.readParcelable(
+                null, android.content.pm.ParceledListSlice.class);
         mRemoteCreateEntry = in.readTypedObject(RemoteEntry.CREATOR);
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeTypedList(mCreateEntries);
+        dest.writeParcelable(mCreateEntries, flags);
         dest.writeTypedObject(mRemoteCreateEntry, flags);
     }
 
@@ -76,7 +77,7 @@
             };
 
     /* package-private */ BeginCreateCredentialResponse(
-            @NonNull List<CreateEntry> createEntries,
+            @NonNull ParceledListSlice<CreateEntry> createEntries,
             @Nullable RemoteEntry remoteCreateEntry) {
         this.mCreateEntries = createEntries;
         com.android.internal.util.AnnotationValidations.validate(
@@ -86,7 +87,7 @@
 
     /** Returns the list of create entries to be displayed on the UI. */
     public @NonNull List<CreateEntry> getCreateEntries() {
-        return mCreateEntries;
+        return mCreateEntries.getList();
     }
 
     /** Returns the remote create entry to be displayed on the UI. */
@@ -159,7 +160,9 @@
          * Builds a new instance of {@link BeginCreateCredentialResponse}.
          */
         public @NonNull BeginCreateCredentialResponse build() {
-            return new BeginCreateCredentialResponse(mCreateEntries, mRemoteCreateEntry);
+            return new BeginCreateCredentialResponse(
+                    new ParceledListSlice<>(mCreateEntries),
+                    mRemoteCreateEntry);
         }
     }
 }
diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java
index e25b686..5ed06ac 100644
--- a/core/java/android/service/credentials/BeginGetCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.content.pm.ParceledListSlice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -35,13 +36,13 @@
  */
 public final class BeginGetCredentialResponse implements Parcelable {
     /** List of credential entries to be displayed on the UI. */
-    private final @NonNull List<CredentialEntry> mCredentialEntries;
+    private final @NonNull ParceledListSlice<CredentialEntry> mCredentialEntries;
 
     /** List of authentication entries to be displayed on the UI. */
-    private final @NonNull List<Action> mAuthenticationEntries;
+    private final @NonNull ParceledListSlice<Action> mAuthenticationEntries;
 
     /** List of provider actions to be displayed on the UI. */
-    private final @NonNull List<Action> mActions;
+    private final @NonNull ParceledListSlice<Action> mActions;
 
     /** Remote credential entry to get the response from a different device. */
     private final @Nullable RemoteEntry mRemoteCredentialEntry;
@@ -51,31 +52,30 @@
      * or {@link Action} to return.
      */
     public BeginGetCredentialResponse() {
-        this(/*credentialEntries=*/new ArrayList<>(),
-                /*authenticationActions=*/new ArrayList<>(),
-                /*actions=*/new ArrayList<>(),
+        this(/*credentialEntries=*/new ParceledListSlice<>(new ArrayList<>()),
+                /*authenticationEntries=*/new ParceledListSlice<>(new ArrayList<>()),
+                /*actions=*/new ParceledListSlice<>(new ArrayList<>()),
                 /*remoteCredentialEntry=*/null);
     }
 
-    private BeginGetCredentialResponse(@NonNull List<CredentialEntry> credentialEntries,
-            @NonNull List<Action> authenticationEntries, @NonNull List<Action> actions,
+    private BeginGetCredentialResponse(
+            @NonNull ParceledListSlice<CredentialEntry> credentialEntries,
+            @NonNull ParceledListSlice<Action> authenticationEntries,
+            @NonNull ParceledListSlice<Action> actions,
             @Nullable RemoteEntry remoteCredentialEntry) {
-        mCredentialEntries = new ArrayList<>(credentialEntries);
-        mAuthenticationEntries = new ArrayList<>(authenticationEntries);
-        mActions = new ArrayList<>(actions);
+        mCredentialEntries = credentialEntries;
+        mAuthenticationEntries = authenticationEntries;
+        mActions = actions;
         mRemoteCredentialEntry = remoteCredentialEntry;
     }
 
     private BeginGetCredentialResponse(@NonNull Parcel in) {
-        List<CredentialEntry> credentialEntries = new ArrayList<>();
-        in.readTypedList(credentialEntries, CredentialEntry.CREATOR);
-        mCredentialEntries = credentialEntries;
-        List<Action> authenticationEntries = new ArrayList<>();
-        in.readTypedList(authenticationEntries, Action.CREATOR);
-        mAuthenticationEntries = authenticationEntries;
-        List<Action> actions = new ArrayList<>();
-        in.readTypedList(actions, Action.CREATOR);
-        mActions = actions;
+        mCredentialEntries = in.readParcelable(
+                null, android.content.pm.ParceledListSlice.class);
+        mAuthenticationEntries = in.readParcelable(
+                null, android.content.pm.ParceledListSlice.class);
+        mActions = in.readParcelable(
+                null, android.content.pm.ParceledListSlice.class);
         mRemoteCredentialEntry = in.readTypedObject(RemoteEntry.CREATOR);
     }
 
@@ -99,9 +99,9 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeTypedList(mCredentialEntries, flags);
-        dest.writeTypedList(mAuthenticationEntries, flags);
-        dest.writeTypedList(mActions, flags);
+        dest.writeParcelable(mCredentialEntries, flags);
+        dest.writeParcelable(mAuthenticationEntries, flags);
+        dest.writeParcelable(mActions, flags);
         dest.writeTypedObject(mRemoteCredentialEntry, flags);
     }
 
@@ -109,21 +109,22 @@
      * Returns the list of credential entries to be displayed on the UI.
      */
     public @NonNull List<CredentialEntry> getCredentialEntries() {
-        return mCredentialEntries;
+        return mCredentialEntries.getList();
     }
 
     /**
      * Returns the list of authentication entries to be displayed on the UI.
      */
     public @NonNull List<Action> getAuthenticationActions() {
-        return mAuthenticationEntries;
+        return mAuthenticationEntries.getList();
     }
 
     /**
      * Returns the list of actions to be displayed on the UI.
      */
     public @NonNull List<Action> getActions() {
-        return mActions;
+
+        return mActions.getList();
     }
 
     /**
@@ -268,8 +269,11 @@
          * Builds a {@link BeginGetCredentialResponse} instance.
          */
         public @NonNull BeginGetCredentialResponse build() {
-            return new BeginGetCredentialResponse(mCredentialEntries, mAuthenticationEntries,
-                    mActions, mRemoteCredentialEntry);
+            return new BeginGetCredentialResponse(
+                    new ParceledListSlice<>(mCredentialEntries),
+                    new ParceledListSlice<>(mAuthenticationEntries),
+                            new ParceledListSlice<>(mActions),
+                    mRemoteCredentialEntry);
         }
     }
 }
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 402da28..828c062 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -45,7 +45,6 @@
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.PluralsMessageFormatter;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -59,7 +58,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
@@ -310,86 +308,6 @@
         return buffer.toString();
     }
 
-    public Diff diff(ZenModeConfig to) {
-        final Diff d = new Diff();
-        if (to == null) {
-            return d.addLine("config", "delete");
-        }
-        if (user != to.user) {
-            d.addLine("user", user, to.user);
-        }
-        if (allowAlarms != to.allowAlarms) {
-            d.addLine("allowAlarms", allowAlarms, to.allowAlarms);
-        }
-        if (allowMedia != to.allowMedia) {
-            d.addLine("allowMedia", allowMedia, to.allowMedia);
-        }
-        if (allowSystem != to.allowSystem) {
-            d.addLine("allowSystem", allowSystem, to.allowSystem);
-        }
-        if (allowCalls != to.allowCalls) {
-            d.addLine("allowCalls", allowCalls, to.allowCalls);
-        }
-        if (allowReminders != to.allowReminders) {
-            d.addLine("allowReminders", allowReminders, to.allowReminders);
-        }
-        if (allowEvents != to.allowEvents) {
-            d.addLine("allowEvents", allowEvents, to.allowEvents);
-        }
-        if (allowRepeatCallers != to.allowRepeatCallers) {
-            d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers);
-        }
-        if (allowMessages != to.allowMessages) {
-            d.addLine("allowMessages", allowMessages, to.allowMessages);
-        }
-        if (allowCallsFrom != to.allowCallsFrom) {
-            d.addLine("allowCallsFrom", allowCallsFrom, to.allowCallsFrom);
-        }
-        if (allowMessagesFrom != to.allowMessagesFrom) {
-            d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom);
-        }
-        if (suppressedVisualEffects != to.suppressedVisualEffects) {
-            d.addLine("suppressedVisualEffects", suppressedVisualEffects,
-                    to.suppressedVisualEffects);
-        }
-        final ArraySet<String> allRules = new ArraySet<>();
-        addKeys(allRules, automaticRules);
-        addKeys(allRules, to.automaticRules);
-        final int N = allRules.size();
-        for (int i = 0; i < N; i++) {
-            final String rule = allRules.valueAt(i);
-            final ZenRule fromRule = automaticRules != null ? automaticRules.get(rule) : null;
-            final ZenRule toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
-            ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule);
-        }
-        ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule);
-
-        if (areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
-            d.addLine("areChannelsBypassingDnd", areChannelsBypassingDnd,
-                    to.areChannelsBypassingDnd);
-        }
-        return d;
-    }
-
-    public static Diff diff(ZenModeConfig from, ZenModeConfig to) {
-        if (from == null) {
-            final Diff d = new Diff();
-            if (to != null) {
-                d.addLine("config", "insert");
-            }
-            return d;
-        }
-        return from.diff(to);
-    }
-
-    private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
-        if (map != null) {
-            for (int i = 0; i < map.size(); i++) {
-                set.add(map.keyAt(i));
-            }
-        }
-    }
-
     public boolean isValid() {
         if (!isValidManualRule(manualRule)) return false;
         final int N = automaticRules.size();
@@ -1922,66 +1840,6 @@
             proto.end(token);
         }
 
-        private static void appendDiff(Diff d, String item, ZenRule from, ZenRule to) {
-            if (d == null) return;
-            if (from == null) {
-                if (to != null) {
-                    d.addLine(item, "insert");
-                }
-                return;
-            }
-            from.appendDiff(d, item, to);
-        }
-
-        private void appendDiff(Diff d, String item, ZenRule to) {
-            if (to == null) {
-                d.addLine(item, "delete");
-                return;
-            }
-            if (enabled != to.enabled) {
-                d.addLine(item, "enabled", enabled, to.enabled);
-            }
-            if (snoozing != to.snoozing) {
-                d.addLine(item, "snoozing", snoozing, to.snoozing);
-            }
-            if (!Objects.equals(name, to.name)) {
-                d.addLine(item, "name", name, to.name);
-            }
-            if (zenMode != to.zenMode) {
-                d.addLine(item, "zenMode", zenMode, to.zenMode);
-            }
-            if (!Objects.equals(conditionId, to.conditionId)) {
-                d.addLine(item, "conditionId", conditionId, to.conditionId);
-            }
-            if (!Objects.equals(condition, to.condition)) {
-                d.addLine(item, "condition", condition, to.condition);
-            }
-            if (!Objects.equals(component, to.component)) {
-                d.addLine(item, "component", component, to.component);
-            }
-            if (!Objects.equals(configurationActivity, to.configurationActivity)) {
-                d.addLine(item, "configActivity", configurationActivity, to.configurationActivity);
-            }
-            if (!Objects.equals(id, to.id)) {
-                d.addLine(item, "id", id, to.id);
-            }
-            if (creationTime != to.creationTime) {
-                d.addLine(item, "creationTime", creationTime, to.creationTime);
-            }
-            if (!Objects.equals(enabler, to.enabler)) {
-                d.addLine(item, "enabler", enabler, to.enabler);
-            }
-            if (!Objects.equals(zenPolicy, to.zenPolicy)) {
-                d.addLine(item, "zenPolicy", zenPolicy, to.zenPolicy);
-            }
-            if (modified != to.modified) {
-                d.addLine(item, "modified", modified, to.modified);
-            }
-            if (!Objects.equals(pkg, to.pkg)) {
-                d.addLine(item, "pkg", pkg, to.pkg);
-            }
-        }
-
         @Override
         public boolean equals(@Nullable Object o) {
             if (!(o instanceof ZenRule)) return false;
@@ -2040,40 +1898,6 @@
         };
     }
 
-    public static class Diff {
-        private final ArrayList<String> lines = new ArrayList<>();
-
-        @Override
-        public String toString() {
-            final StringBuilder sb = new StringBuilder("Diff[");
-            final int N = lines.size();
-            for (int i = 0; i < N; i++) {
-                if (i > 0) {
-                    sb.append(",\n");
-                }
-                sb.append(lines.get(i));
-            }
-            return sb.append(']').toString();
-        }
-
-        private Diff addLine(String item, String action) {
-            lines.add(item + ":" + action);
-            return this;
-        }
-
-        public Diff addLine(String item, String subitem, Object from, Object to) {
-            return addLine(item + "." + subitem, from, to);
-        }
-
-        public Diff addLine(String item, Object from, Object to) {
-            return addLine(item, from + "->" + to);
-        }
-
-        public boolean isEmpty() {
-            return lines.isEmpty();
-        }
-    }
-
     /**
      * Determines whether dnd behavior should mute all ringer-controlled sounds
      * This includes notification, ringer and system sounds
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
new file mode 100644
index 0000000..c7b89eb
--- /dev/null
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -0,0 +1,542 @@
+/*
+ * 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.service.notification;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their
+ * subcomponents (automatic and manual ZenRules).
+ * @hide
+ */
+public class ZenModeDiff {
+    /**
+     * Enum representing whether the existence of a config or rule has changed (added or removed,
+     * or "none" meaning there is no change, which may either mean both null, or there exists a
+     * diff in fields rather than add/remove).
+     */
+    @IntDef(value = {
+            NONE,
+            ADDED,
+            REMOVED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ExistenceChange{}
+
+    public static final int NONE = 0;
+    public static final int ADDED = 1;
+    public static final int REMOVED = 2;
+
+    /**
+     * Diff class representing an individual field diff.
+     * @param <T> The type of the field.
+     */
+    public static class FieldDiff<T> {
+        private final T mFrom;
+        private final T mTo;
+
+        /**
+         * Constructor to create a FieldDiff object with the given values.
+         * @param from from (old) value
+         * @param to to (new) value
+         */
+        public FieldDiff(@Nullable T from, @Nullable T to) {
+            mFrom = from;
+            mTo = to;
+        }
+
+        /**
+         * Get the "from" value
+         */
+        public T from() {
+            return mFrom;
+        }
+
+        /**
+         * Get the "to" value
+         */
+        public T to() {
+            return mTo;
+        }
+
+        /**
+         * Get the string representation of this field diff, in the form of "from->to".
+         */
+        @Override
+        public String toString() {
+            return mFrom + "->" + mTo;
+        }
+
+        /**
+         * Returns whether this represents an actual diff.
+         */
+        public boolean hasDiff() {
+            // note that Objects.equals handles null values gracefully.
+            return !Objects.equals(mFrom, mTo);
+        }
+    }
+
+    /**
+     * Base diff class that contains info about whether something was added, and a set of named
+     * fields that changed.
+     * Extend for diffs of specific types of objects.
+     */
+    private abstract static class BaseDiff {
+        // Whether the diff was added or removed
+        @ExistenceChange private int mExists = NONE;
+
+        // Map from field name to diffs for any standalone fields in the object.
+        private ArrayMap<String, FieldDiff> mFields = new ArrayMap<>();
+
+        // Functions for actually diffing objects and string representations have to be implemented
+        // by subclasses.
+
+        /**
+         * Return whether this diff represents any changes.
+         */
+        public abstract boolean hasDiff();
+
+        /**
+         * Return a string representation of the diff.
+         */
+        public abstract String toString();
+
+        /**
+         * Constructor that takes the two objects meant to be compared. This constructor sets
+         * whether there is an existence change (added or removed).
+         * @param from previous Object
+         * @param to new Object
+         */
+        BaseDiff(Object from, Object to) {
+            if (from == null) {
+                if (to != null) {
+                    mExists = ADDED;
+                }
+                // If both are null, there isn't an existence change; callers/inheritors must handle
+                // the both null case.
+            } else if (to == null) {
+                // in this case, we know that from != null
+                mExists = REMOVED;
+            }
+
+            // Subclasses should implement the actual diffing functionality in their own
+            // constructors.
+        }
+
+        /**
+         * Add a diff for a specific field to the map.
+         * @param name field name
+         * @param diff FieldDiff object representing the diff
+         */
+        final void addField(String name, FieldDiff diff) {
+            mFields.put(name, diff);
+        }
+
+        /**
+         * Returns whether this diff represents a config being newly added.
+         */
+        public final boolean wasAdded() {
+            return mExists == ADDED;
+        }
+
+        /**
+         * Returns whether this diff represents a config being removed.
+         */
+        public final boolean wasRemoved() {
+            return mExists == REMOVED;
+        }
+
+        /**
+         * Returns whether this diff represents an object being either added or removed.
+         */
+        public final boolean hasExistenceChange() {
+            return mExists != NONE;
+        }
+
+        /**
+         * Returns whether there are any individual field diffs.
+         */
+        public final boolean hasFieldDiffs() {
+            return mFields.size() > 0;
+        }
+
+        /**
+         * Returns the diff for the specific named field if it exists
+         */
+        public final FieldDiff getDiffForField(String name) {
+            return mFields.getOrDefault(name, null);
+        }
+
+        /**
+         * Get the set of all field names with some diff.
+         */
+        public final Set<String> fieldNamesWithDiff() {
+            return mFields.keySet();
+        }
+    }
+
+    /**
+     * Diff class representing a diff between two ZenModeConfigs.
+     */
+    public static class ConfigDiff extends BaseDiff {
+        // Rules. Automatic rule map is keyed by the rule name.
+        private final ArrayMap<String, RuleDiff> mAutomaticRulesDiff = new ArrayMap<>();
+        private RuleDiff mManualRuleDiff;
+
+        // Helpers for string generation
+        private static final String ALLOW_CALLS_FROM_FIELD = "allowCallsFrom";
+        private static final String ALLOW_MESSAGES_FROM_FIELD = "allowMessagesFrom";
+        private static final String ALLOW_CONVERSATIONS_FROM_FIELD = "allowConversationsFrom";
+        private static final Set<String> PEOPLE_TYPE_FIELDS =
+                Set.of(ALLOW_CALLS_FROM_FIELD, ALLOW_MESSAGES_FROM_FIELD);
+
+        /**
+         * Create a diff that contains diffs between the "from" and "to" ZenModeConfigs.
+         *
+         * @param from previous ZenModeConfig
+         * @param to   new ZenModeConfig
+         */
+        public ConfigDiff(ZenModeConfig from, ZenModeConfig to) {
+            super(from, to);
+            // If both are null skip
+            if (from == null && to == null) {
+                return;
+            }
+            if (hasExistenceChange()) {
+                // either added or removed; return here. otherwise (they're not both null) there's
+                // field diffs.
+                return;
+            }
+
+            // Now we compare all the fields, knowing there's a diff and that neither is null
+            if (from.user != to.user) {
+                addField("user", new FieldDiff<>(from.user, to.user));
+            }
+            if (from.allowAlarms != to.allowAlarms) {
+                addField("allowAlarms", new FieldDiff<>(from.allowAlarms, to.allowAlarms));
+            }
+            if (from.allowMedia != to.allowMedia) {
+                addField("allowMedia", new FieldDiff<>(from.allowMedia, to.allowMedia));
+            }
+            if (from.allowSystem != to.allowSystem) {
+                addField("allowSystem", new FieldDiff<>(from.allowSystem, to.allowSystem));
+            }
+            if (from.allowCalls != to.allowCalls) {
+                addField("allowCalls", new FieldDiff<>(from.allowCalls, to.allowCalls));
+            }
+            if (from.allowReminders != to.allowReminders) {
+                addField("allowReminders",
+                        new FieldDiff<>(from.allowReminders, to.allowReminders));
+            }
+            if (from.allowEvents != to.allowEvents) {
+                addField("allowEvents", new FieldDiff<>(from.allowEvents, to.allowEvents));
+            }
+            if (from.allowRepeatCallers != to.allowRepeatCallers) {
+                addField("allowRepeatCallers",
+                        new FieldDiff<>(from.allowRepeatCallers, to.allowRepeatCallers));
+            }
+            if (from.allowMessages != to.allowMessages) {
+                addField("allowMessages",
+                        new FieldDiff<>(from.allowMessages, to.allowMessages));
+            }
+            if (from.allowConversations != to.allowConversations) {
+                addField("allowConversations",
+                        new FieldDiff<>(from.allowConversations, to.allowConversations));
+            }
+            if (from.allowCallsFrom != to.allowCallsFrom) {
+                addField("allowCallsFrom",
+                        new FieldDiff<>(from.allowCallsFrom, to.allowCallsFrom));
+            }
+            if (from.allowMessagesFrom != to.allowMessagesFrom) {
+                addField("allowMessagesFrom",
+                        new FieldDiff<>(from.allowMessagesFrom, to.allowMessagesFrom));
+            }
+            if (from.allowConversationsFrom != to.allowConversationsFrom) {
+                addField("allowConversationsFrom",
+                        new FieldDiff<>(from.allowConversationsFrom, to.allowConversationsFrom));
+            }
+            if (from.suppressedVisualEffects != to.suppressedVisualEffects) {
+                addField("suppressedVisualEffects",
+                        new FieldDiff<>(from.suppressedVisualEffects, to.suppressedVisualEffects));
+            }
+            if (from.areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
+                addField("areChannelsBypassingDnd",
+                        new FieldDiff<>(from.areChannelsBypassingDnd, to.areChannelsBypassingDnd));
+            }
+
+            // Compare automatic and manual rules
+            final ArraySet<String> allRules = new ArraySet<>();
+            addKeys(allRules, from.automaticRules);
+            addKeys(allRules, to.automaticRules);
+            final int num = allRules.size();
+            for (int i = 0; i < num; i++) {
+                final String rule = allRules.valueAt(i);
+                final ZenModeConfig.ZenRule
+                        fromRule = from.automaticRules != null ? from.automaticRules.get(rule)
+                        : null;
+                final ZenModeConfig.ZenRule
+                        toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
+                RuleDiff ruleDiff = new RuleDiff(fromRule, toRule);
+                if (ruleDiff.hasDiff()) {
+                    mAutomaticRulesDiff.put(rule, ruleDiff);
+                }
+            }
+            // If there's no diff this may turn out to be null, but that's also fine
+            RuleDiff manualRuleDiff = new RuleDiff(from.manualRule, to.manualRule);
+            if (manualRuleDiff.hasDiff()) {
+                mManualRuleDiff = manualRuleDiff;
+            }
+        }
+
+        private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
+            if (map != null) {
+                for (int i = 0; i < map.size(); i++) {
+                    set.add(map.keyAt(i));
+                }
+            }
+        }
+
+        /**
+         * Returns whether this diff object contains any diffs in any field.
+         */
+        @Override
+        public boolean hasDiff() {
+            return hasExistenceChange()
+                    || hasFieldDiffs()
+                    || mManualRuleDiff != null
+                    || mAutomaticRulesDiff.size() > 0;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder("Diff[");
+            if (!hasDiff()) {
+                sb.append("no changes");
+            }
+
+            // If added or deleted, then that's just the end of it
+            if (hasExistenceChange()) {
+                if (wasAdded()) {
+                    sb.append("added");
+                } else if (wasRemoved()) {
+                    sb.append("removed");
+                }
+            }
+
+            // Handle top-level field change
+            boolean first = true;
+            for (String key : fieldNamesWithDiff()) {
+                FieldDiff diff = getDiffForField(key);
+                if (diff == null) {
+                    // this shouldn't happen, but
+                    continue;
+                }
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(",\n");
+                }
+
+                // Some special handling for people- and conversation-type fields for readability
+                if (PEOPLE_TYPE_FIELDS.contains(key)) {
+                    sb.append(key);
+                    sb.append(":");
+                    sb.append(ZenModeConfig.sourceToString((int) diff.from()));
+                    sb.append("->");
+                    sb.append(ZenModeConfig.sourceToString((int) diff.to()));
+                } else if (key.equals(ALLOW_CONVERSATIONS_FROM_FIELD)) {
+                    sb.append(key);
+                    sb.append(":");
+                    sb.append(ZenPolicy.conversationTypeToString((int) diff.from()));
+                    sb.append("->");
+                    sb.append(ZenPolicy.conversationTypeToString((int) diff.to()));
+                } else {
+                    sb.append(key);
+                    sb.append(":");
+                    sb.append(diff);
+                }
+            }
+
+            // manual rule
+            if (mManualRuleDiff != null && mManualRuleDiff.hasDiff()) {
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(",\n");
+                }
+                sb.append("manualRule:");
+                sb.append(mManualRuleDiff);
+            }
+
+            // automatic rules
+            for (String rule : mAutomaticRulesDiff.keySet()) {
+                RuleDiff diff = mAutomaticRulesDiff.get(rule);
+                if (diff != null && diff.hasDiff()) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        sb.append(",\n");
+                    }
+                    sb.append("automaticRule[");
+                    sb.append(rule);
+                    sb.append("]:");
+                    sb.append(diff);
+                }
+            }
+
+            return sb.append(']').toString();
+        }
+
+        /**
+         * Get the diff in manual rule, if it exists.
+         */
+        public RuleDiff getManualRuleDiff() {
+            return mManualRuleDiff;
+        }
+
+        /**
+         * Get the full map of automatic rule diffs, or null if there are no diffs.
+         */
+        public ArrayMap<String, RuleDiff> getAllAutomaticRuleDiffs() {
+            return (mAutomaticRulesDiff.size() > 0) ? mAutomaticRulesDiff : null;
+        }
+    }
+
+    /**
+     * Diff class representing a change between two ZenRules.
+     */
+    public static class RuleDiff extends BaseDiff {
+        /**
+         * Create a RuleDiff representing the difference between two ZenRule objects.
+         * @param from previous ZenRule
+         * @param to new ZenRule
+         * @return The diff between the two given ZenRules
+         */
+        public RuleDiff(ZenModeConfig.ZenRule from, ZenModeConfig.ZenRule to) {
+            super(from, to);
+            // Short-circuit the both-null case
+            if (from == null && to == null) {
+                return;
+            }
+            // Return if the diff was added or removed
+            if (hasExistenceChange()) {
+                return;
+            }
+
+            if (from.enabled != to.enabled) {
+                addField("enabled", new FieldDiff<>(from.enabled, to.enabled));
+            }
+            if (from.snoozing != to.snoozing) {
+                addField("snoozing", new FieldDiff<>(from.snoozing, to.snoozing));
+            }
+            if (!Objects.equals(from.name, to.name)) {
+                addField("name", new FieldDiff<>(from.name, to.name));
+            }
+            if (from.zenMode != to.zenMode) {
+                addField("zenMode", new FieldDiff<>(from.zenMode, to.zenMode));
+            }
+            if (!Objects.equals(from.conditionId, to.conditionId)) {
+                addField("conditionId", new FieldDiff<>(from.conditionId, to.conditionId));
+            }
+            if (!Objects.equals(from.condition, to.condition)) {
+                addField("condition", new FieldDiff<>(from.condition, to.condition));
+            }
+            if (!Objects.equals(from.component, to.component)) {
+                addField("component", new FieldDiff<>(from.component, to.component));
+            }
+            if (!Objects.equals(from.configurationActivity, to.configurationActivity)) {
+                addField("configurationActivity", new FieldDiff<>(
+                        from.configurationActivity, to.configurationActivity));
+            }
+            if (!Objects.equals(from.id, to.id)) {
+                addField("id", new FieldDiff<>(from.id, to.id));
+            }
+            if (from.creationTime != to.creationTime) {
+                addField("creationTime",
+                        new FieldDiff<>(from.creationTime, to.creationTime));
+            }
+            if (!Objects.equals(from.enabler, to.enabler)) {
+                addField("enabler", new FieldDiff<>(from.enabler, to.enabler));
+            }
+            if (!Objects.equals(from.zenPolicy, to.zenPolicy)) {
+                addField("zenPolicy", new FieldDiff<>(from.zenPolicy, to.zenPolicy));
+            }
+            if (from.modified != to.modified) {
+                addField("modified", new FieldDiff<>(from.modified, to.modified));
+            }
+            if (!Objects.equals(from.pkg, to.pkg)) {
+                addField("pkg", new FieldDiff<>(from.pkg, to.pkg));
+            }
+        }
+
+        /**
+         * Returns whether this object represents an actual diff.
+         */
+        @Override
+        public boolean hasDiff() {
+            return hasExistenceChange() || hasFieldDiffs();
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder("ZenRuleDiff{");
+            // If there's no diff, probably we haven't actually let this object continue existing
+            // but might as well handle this case.
+            if (!hasDiff()) {
+                sb.append("no changes");
+            }
+
+            // If added or deleted, then that's just the end of it
+            if (hasExistenceChange()) {
+                if (wasAdded()) {
+                    sb.append("added");
+                } else if (wasRemoved()) {
+                    sb.append("removed");
+                }
+            }
+
+            // Go through all of the individual fields
+            boolean first = true;
+            for (String key : fieldNamesWithDiff()) {
+                FieldDiff diff = getDiffForField(key);
+                if (diff == null) {
+                    // this shouldn't happen, but
+                    continue;
+                }
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(", ");
+                }
+
+                sb.append(key);
+                sb.append(":");
+                sb.append(diff);
+            }
+
+            return sb.append("}").toString();
+        }
+    }
+}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 8688a18..24c96ea 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1573,16 +1573,6 @@
         }
 
         @Override
-        public void onError(int status) {
-            Slog.i(TAG, "onError: " + status);
-            // TODO(b/271534248): This is a workaround before the sound trigger uses the new error
-            // method.
-            Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE,
-                    new SoundTriggerFailure(SoundTriggerFailure.ERROR_CODE_UNKNOWN,
-                            "Sound trigger error")).sendToTarget();
-        }
-
-        @Override
         public void onHotwordDetectionServiceFailure(
                 HotwordDetectionServiceFailure hotwordDetectionServiceFailure) {
             Slog.v(TAG, "onHotwordDetectionServiceFailure: " + hotwordDetectionServiceFailure);
@@ -1605,6 +1595,12 @@
         }
 
         @Override
+        public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) {
+            Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE,
+                    Objects.requireNonNull(soundTriggerFailure)).sendToTarget();
+        }
+
+        @Override
         public void onUnknownFailure(String errorMessage) throws RemoteException {
             Slog.v(TAG, "onUnknownFailure: " + errorMessage);
             Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE,
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index eac7aee..7ab4faf 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -234,14 +234,6 @@
         }
 
         @Override
-        public void onError(int status) throws RemoteException {
-            if (DEBUG) {
-                Slog.i(TAG, "Ignored #onError (" + status + ") event");
-            }
-            // TODO: Check if we still need to implement this method with DetectorFailure mechanism.
-        }
-
-        @Override
         public void onHotwordDetectionServiceFailure(
                 HotwordDetectionServiceFailure hotwordDetectionServiceFailure)
                 throws RemoteException {
@@ -265,6 +257,13 @@
         }
 
         @Override
+        public void onSoundTriggerFailure(SoundTriggerFailure onSoundTriggerFailure)
+                throws RemoteException {
+            // It should never be called here.
+            Slog.wtf(TAG, "Unexpected STFailure in software detector: " + onSoundTriggerFailure);
+        }
+
+        @Override
         public void onUnknownFailure(String errorMessage) throws RemoteException {
             Slog.v(TAG, "onUnknownFailure: " + errorMessage);
             Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
diff --git a/core/java/android/service/voice/SoundTriggerFailure.java b/core/java/android/service/voice/SoundTriggerFailure.java
index 5560800..2ce5e5d 100644
--- a/core/java/android/service/voice/SoundTriggerFailure.java
+++ b/core/java/android/service/voice/SoundTriggerFailure.java
@@ -73,18 +73,28 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface SoundTriggerErrorCode {}
 
-    private int mErrorCode = ERROR_CODE_UNKNOWN;
-    private String mErrorMessage = "Unknown";
+    private final int mErrorCode;
+    private final String mErrorMessage;
 
     /**
      * @hide
      */
     @TestApi
-    public SoundTriggerFailure(int errorCode, @NonNull String errorMessage) {
+    public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode,
+            @NonNull String errorMessage) {
         if (TextUtils.isEmpty(errorMessage)) {
             throw new IllegalArgumentException("errorMessage is empty or null.");
         }
-        mErrorCode = errorCode;
+        switch (errorCode) {
+            case ERROR_CODE_UNKNOWN:
+            case ERROR_CODE_MODULE_DIED:
+            case ERROR_CODE_RECOGNITION_RESUME_FAILED:
+            case ERROR_CODE_UNEXPECTED_PREEMPTION:
+                mErrorCode = errorCode;
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid ErrorCode: " + errorCode);
+        }
         mErrorMessage = errorMessage;
     }
 
@@ -110,13 +120,14 @@
     @FailureSuggestedAction.FailureSuggestedActionDef
     public int getSuggestedAction() {
         switch (mErrorCode) {
+            case ERROR_CODE_UNKNOWN:
             case ERROR_CODE_MODULE_DIED:
             case ERROR_CODE_UNEXPECTED_PREEMPTION:
                 return FailureSuggestedAction.RECREATE_DETECTOR;
             case ERROR_CODE_RECOGNITION_RESUME_FAILED:
                 return FailureSuggestedAction.RESTART_RECOGNITION;
             default:
-                return FailureSuggestedAction.NONE;
+                throw new AssertionError("Unexpected error code");
         }
     }
 
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index b4f5ff1..93b7964 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -391,12 +391,6 @@
         }
 
         @Override
-        public void onError(int status) throws RemoteException {
-            Slog.v(TAG, "Initialization Error: (" + status + ")");
-            // Do nothing
-        }
-
-        @Override
         public void onHotwordDetectionServiceFailure(
                 HotwordDetectionServiceFailure hotwordDetectionServiceFailure)
                 throws RemoteException {
@@ -420,6 +414,11 @@
         }
 
         @Override
+        public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) {
+            Slog.wtf(TAG, "Unexpected STFailure in VisualQueryDetector" + soundTriggerFailure);
+        }
+
+        @Override
         public void onUnknownFailure(String errorMessage) throws RemoteException {
             Slog.v(TAG, "onUnknownFailure: " + errorMessage);
             Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index fcc64b0..68cce4a 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -50,6 +50,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceActionCheckCallback;
 import com.android.internal.app.IVoiceInteractionManagerService;
@@ -200,6 +201,9 @@
 
     private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>();
 
+    // True if any of the createAOHD methods should use the test ST module.
+    @GuardedBy("mLock")
+    private boolean mTestModuleForAlwaysOnHotwordDetectorEnabled = false;
 
     private void onDetectorRemoteException(@NonNull IBinder token, int detectorType) {
         Log.d(TAG, "onDetectorRemoteException for " + HotwordDetector.detectorTypeToString(
@@ -512,14 +516,14 @@
         Objects.requireNonNull(callback);
         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
                 /* supportHotwordDetectionService= */ false, /* options= */ null,
-                /* sharedMemory= */ null, /* moduleProperties */ null, executor, callback);
+                /* sharedMemory= */ null, /* moduleProperties= */ null, executor, callback);
     }
 
     /**
      * Same as {@link createAlwaysOnHotwordDetector(String, Locale, Executor,
      * AlwaysOnHotwordDetector.Callback)}, but allow explicit selection of the underlying ST
      * module to attach to.
-     * Use {@link listModuleProperties} to get available modules to attach to.
+     * Use {@link #listModuleProperties()} to get available modules to attach to.
      * @hide
      */
     @TestApi
@@ -645,14 +649,14 @@
         Objects.requireNonNull(callback);
         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
                 /* supportHotwordDetectionService= */ true, options, sharedMemory,
-                /* moduleProperties */ null, executor, callback);
+                /* moduleProperties= */ null, executor, callback);
     }
 
     /**
      * Same as {@link createAlwaysOnHotwordDetector(String, Locale,
      * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)},
      * but allow explicit selection of the underlying ST module to attach to.
-     * Use {@link listModuleProperties} to get available modules to attach to.
+     * Use {@link #listModuleProperties()} to get available modules to attach to.
      * @hide
      */
     @TestApi
@@ -717,6 +721,10 @@
 
             try {
                 dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
+                // Check if we are currently overridden, and should use the test module.
+                if (mTestModuleForAlwaysOnHotwordDetectorEnabled) {
+                    moduleProperties = getTestModuleProperties();
+                }
                 // If moduleProperties is null, the default STModule is used.
                 dspDetector.initialize(options, sharedMemory, moduleProperties);
             } catch (Exception e) {
@@ -990,6 +998,44 @@
         return mKeyphraseEnrollmentInfo;
     }
 
+
+    /**
+     * Configure {@link createAlwaysOnHotwordDetector(String, Locale,
+     * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
+     * and similar overloads to utilize the test SoundTrigger module instead of the
+     * actual DSP module.
+     * @param isEnabled - {@code true} if subsequently created {@link AlwaysOnHotwordDetector}
+     * objects should attach to a test module. {@code false} if subsequently created
+     * {@link AlwaysOnHotwordDetector} should attach to the actual DSP module.
+     * @hide
+     */
+    @TestApi
+    public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled) {
+        synchronized (mLock) {
+            mTestModuleForAlwaysOnHotwordDetectorEnabled = isEnabled;
+        }
+    }
+
+    /**
+     * Get the {@link SoundTrigger.ModuleProperties} representing the fake
+     * STHAL to attach to via {@link createAlwaysOnHotwordDetector(String, Locale,
+     * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} and
+     * similar overloads for test purposes.
+     * @return ModuleProperties to use for test purposes.
+     */
+    private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() {
+        var moduleProps = listModuleProperties()
+                .stream()
+                .filter((SoundTrigger.ModuleProperties prop)
+                        -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH))
+                .findFirst()
+                .orElse(null);
+        if (moduleProps == null) {
+            throw new IllegalStateException("Fake ST HAL should always be available");
+        }
+        return moduleProps;
+    }
+
     /**
      * Checks if a given keyphrase and locale are supported to create an
      * {@link AlwaysOnHotwordDetector}.
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index 4c51be0..f1ae22e 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -32,6 +32,8 @@
     oneway void setDisplayPadding(in Rect padding);
     @UnsupportedAppUsage
     oneway void setVisibility(boolean visible);
+    oneway void onScreenTurningOn();
+    oneway void onScreenTurnedOn();
     oneway void setInAmbientMode(boolean inAmbientDisplay, long animationDuration);
     @UnsupportedAppUsage
     oneway void dispatchPointer(in MotionEvent event);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0b947fc..77bbeb5 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -168,6 +168,7 @@
     private static final int MSG_ZOOM = 10100;
     private static final int MSG_RESIZE_PREVIEW = 10110;
     private static final int MSG_REPORT_SHOWN = 10150;
+    private static final int MSG_UPDATE_SCREEN_TURNING_ON = 10170;
     private static final int MSG_UPDATE_DIMMING = 10200;
     private static final int MSG_WALLPAPER_FLAGS_CHANGED = 10210;
 
@@ -213,6 +214,16 @@
 
         boolean mInitializing = true;
         boolean mVisible;
+        /**
+         * Whether the screen is turning on.
+         * After the display is powered on, brightness is initially off. It is turned on only after
+         * all windows have been drawn, and sysui notifies that it's ready (See
+         * {@link com.android.internal.policy.IKeyguardDrawnCallback}).
+         * As some wallpapers use visibility as a signal to start animations, this makes sure
+         * {@link Engine#onVisibilityChanged} is invoked only when the display is both on and
+         * visible (with brightness on).
+         */
+        private boolean mIsScreenTurningOn;
         boolean mReportedVisible;
         boolean mDestroyed;
         // Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false
@@ -1018,6 +1029,7 @@
                     out.print(" mDestroyed="); out.println(mDestroyed);
             out.print(prefix); out.print("mVisible="); out.print(mVisible);
                     out.print(" mReportedVisible="); out.println(mReportedVisible);
+                    out.print(" mIsScreenTurningOn="); out.println(mIsScreenTurningOn);
             out.print(prefix); out.print("mDisplay="); out.println(mDisplay);
             out.print(prefix); out.print("mCreated="); out.print(mCreated);
                     out.print(" mSurfaceCreated="); out.print(mSurfaceCreated);
@@ -1549,6 +1561,13 @@
             }
         }
 
+        void onScreenTurningOnChanged(boolean isScreenTurningOn) {
+            if (!mDestroyed) {
+                mIsScreenTurningOn = isScreenTurningOn;
+                reportVisibility(false);
+            }
+        }
+
         void doVisibilityChanged(boolean visible) {
             if (!mDestroyed) {
                 mVisible = visible;
@@ -1565,9 +1584,10 @@
                 return;
             }
             if (!mDestroyed) {
-                mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN :
-                        mDisplay.getCommittedState();
-                boolean visible = mVisible && mDisplayState != Display.STATE_OFF;
+                mDisplayState =
+                        mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getCommittedState();
+                boolean displayVisible = Display.isOnState(mDisplayState) && !mIsScreenTurningOn;
+                boolean visible = mVisible && displayVisible;
                 if (DEBUG) {
                     Log.v(
                             TAG,
@@ -2486,6 +2506,20 @@
             }
         }
 
+        public void updateScreenTurningOn(boolean isScreenTurningOn) {
+            Message msg = mCaller.obtainMessageBO(MSG_UPDATE_SCREEN_TURNING_ON, isScreenTurningOn,
+                    null);
+            mCaller.sendMessage(msg);
+        }
+
+        public void onScreenTurningOn() throws RemoteException {
+            updateScreenTurningOn(true);
+        }
+
+        public void onScreenTurnedOn() throws RemoteException {
+            updateScreenTurningOn(false);
+        }
+
         @Override
         public void executeMessage(Message message) {
             switch (message.what) {
@@ -2530,6 +2564,13 @@
                             + ": " + message.arg1);
                     mEngine.doVisibilityChanged(message.arg1 != 0);
                     break;
+                case MSG_UPDATE_SCREEN_TURNING_ON:
+                    if (DEBUG) {
+                        Log.v(TAG,
+                                message.arg1 != 0 ? "Screen turning on" : "Screen turned on");
+                    }
+                    mEngine.onScreenTurningOnChanged(/* isScreenTurningOn= */ message.arg1 != 0);
+                    break;
                 case MSG_WALLPAPER_OFFSETS: {
                     mEngine.doOffsetsChanged(true);
                 } break;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index c329508..d87198a0 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -128,6 +128,12 @@
      */
     public static final String SETTINGS_ENABLE_SPA_PHASE2 = "settings_enable_spa_phase2";
 
+    /**
+     * Enable the SPA metrics writing.
+     * @hide
+     */
+    public static final String SETTINGS_ENABLE_SPA_METRICS = "settings_enable_spa_metrics";
+
     /** Flag to enable/disable adb log metrics
      *  @hide
      */
@@ -226,6 +232,7 @@
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_PHASE2, "false");
+        DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "false");
         DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
         DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 9a93e1b..d06b0ce 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -354,6 +354,15 @@
     }
 
     /** @hide Just for debugging; not internationalized. */
+    public static void formatDuration(long time, long now, StringBuilder sb) {
+        if (time == 0) {
+            sb.append("--");
+            return;
+        }
+        formatDuration(time-now, sb, 0);
+    }
+
+    /** @hide Just for debugging; not internationalized. */
     public static void formatDuration(long time, long now, PrintWriter pw) {
         if (time == 0) {
             pw.print("--");
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 8c4e90c..5dd2d82 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -785,12 +785,13 @@
             DisplayEventReceiver.VsyncEventData vsyncEventData) {
         final long startNanos;
         final long frameIntervalNanos = vsyncEventData.frameInterval;
+        boolean resynced = false;
         try {
+            FrameTimeline timeline = mFrameData.update(frameTimeNanos, vsyncEventData);
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                        "Choreographer#doFrame " + vsyncEventData.preferredFrameTimeline().vsyncId);
+                Trace.traceBegin(
+                        Trace.TRACE_TAG_VIEW, "Choreographer#doFrame " + timeline.mVsyncId);
             }
-            mFrameData.update(frameTimeNanos, vsyncEventData);
             synchronized (mLock) {
                 if (!mFrameScheduled) {
                     traceMessage("Frame not scheduled");
@@ -828,7 +829,9 @@
                                     + " ms in the past.");
                         }
                     }
-                    mFrameData.update(frameTimeNanos, mDisplayEventReceiver, jitterNanos);
+                    timeline = mFrameData.update(
+                            frameTimeNanos, mDisplayEventReceiver, jitterNanos);
+                    resynced = true;
                 }
 
                 if (frameTimeNanos < mLastFrameTimeNanos) {
@@ -860,6 +863,12 @@
                 mLastVsyncEventData = vsyncEventData;
             }
 
+            if (resynced && Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                String message = String.format("Choreographer#doFrame - resynced to %d in %.1fms",
+                        timeline.mVsyncId, (timeline.mDeadlineNanos - startNanos) * 0.000001f);
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW, message);
+            }
+
             AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
 
             mFrameInfo.markInputHandlingStart();
@@ -875,6 +884,9 @@
             doCallbacks(Choreographer.CALLBACK_COMMIT, frameIntervalNanos);
         } finally {
             AnimationUtils.unlockAnimationClock();
+            if (resynced) {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
         }
 
@@ -1149,7 +1161,8 @@
          * Update the frame data with a {@code DisplayEventReceiver.VsyncEventData} received from
          * native.
          */
-        void update(long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) {
+        FrameTimeline update(
+                long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) {
             if (vsyncEventData.frameTimelines.length != mFrameTimelines.length) {
                 throw new IllegalStateException(
                         "Length of native frame timelines received does not match Java. Did "
@@ -1164,6 +1177,7 @@
                 mFrameTimelines[i].update(frameTimeline.vsyncId,
                         frameTimeline.expectedPresentationTime, frameTimeline.deadline);
             }
+            return mFrameTimelines[mPreferredFrameTimelineIndex];
         }
 
         /**
@@ -1171,7 +1185,7 @@
          *
          * @param jitterNanos currentTime - frameTime
          */
-        void update(
+        FrameTimeline update(
                 long frameTimeNanos, DisplayEventReceiver displayEventReceiver, long jitterNanos) {
             int newPreferredIndex = 0;
             final long minimumDeadline =
@@ -1192,6 +1206,7 @@
             } else {
                 update(frameTimeNanos, newPreferredIndex);
             }
+            return mFrameTimelines[mPreferredFrameTimelineIndex];
         }
 
         void update(long frameTimeNanos, int newPreferredFrameTimelineIndex) {
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index 0b4adae..a8e68b71 100644
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -100,6 +101,7 @@
      * @return The display id associated with the event.
      * @hide
      */
+    @TestApi
     public abstract int getDisplayId();
 
     /**
@@ -107,6 +109,7 @@
      * @param displayId
      * @hide
      */
+    @TestApi
     public abstract void setDisplayId(int displayId);
     /**
      * Copies the event.
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index b6d9400..858da55 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -2100,6 +2100,7 @@
     }
 
     /** @hide */
+    @TestApi
     @Override
     public final int getDisplayId() {
         return mDisplayId;
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 95484fa..16c1335 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -303,6 +303,7 @@
     /** @hide */
     public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
             @NonNull WindowlessWindowManager wwm, @NonNull String callsite) {
+        mSurfaceControl = wwm.mRootSurface;
         mWm = wwm;
         mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout());
         mCloseGuard.openWithCallSite("release", callsite);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2f5cd54..055b5cb 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6946,6 +6946,7 @@
                 return;
             }
             final boolean needsStylusPointerIcon = event.isStylusPointer()
+                    && event.isHoverEvent()
                     && mInputManager.isStylusPointerIconEnabled();
             if (needsStylusPointerIcon || event.isFromSource(InputDevice.SOURCE_MOUSE)) {
                 if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 1fac142..390503b 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -62,7 +62,7 @@
             in IAccessibilityInteractionConnection connection);
 
     void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient client,
-        in AccessibilityServiceInfo info, int flags);
+        in AccessibilityServiceInfo info, int userId, int flags);
 
     void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
 
diff --git a/core/java/android/view/translation/Translator.java b/core/java/android/view/translation/Translator.java
index 70db6e5..50249da 100644
--- a/core/java/android/view/translation/Translator.java
+++ b/core/java/android/view/translation/Translator.java
@@ -18,7 +18,6 @@
 
 import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL;
 import static android.view.translation.TranslationManager.SYNC_CALLS_TIMEOUT_MS;
-import static android.view.translation.UiTranslationController.DEBUG;
 
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
@@ -402,7 +401,7 @@
 
         @Override
         public void onTranslationResponse(TranslationResponse response) throws RemoteException {
-            if (DEBUG) {
+            if (Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)) {
                 Log.i(TAG, "onTranslationResponse called.");
             }
             final Runnable runnable =
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 514df59..140e3f1 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -122,8 +122,9 @@
             Log.i(TAG, "Cannot update " + stateToString(state) + " for destroyed " + mActivity);
             return;
         }
+        boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
         Log.i(TAG, "updateUiTranslationState state: " + stateToString(state)
-                + (DEBUG ? (", views: " + views + ", spec: " + uiTranslationSpec) : ""));
+                + (isLoggable ? (", views: " + views + ", spec: " + uiTranslationSpec) : ""));
         synchronized (mLock) {
             mCurrentState = state;
             if (views != null) {
@@ -237,7 +238,7 @@
             }
             pw.print(outerPrefix); pw.print("padded views: "); pw.println(mViewsToPadContent);
         }
-        if (DEBUG) {
+        if (Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)) {
             dumpViewByTraversal(outerPrefix, pw);
         }
     }
@@ -345,6 +346,7 @@
      */
     private void onVirtualViewTranslationCompleted(
             SparseArray<LongSparseArray<ViewTranslationResponse>> translatedResult) {
+        boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
         if (mActivity.isDestroyed()) {
             Log.v(TAG, "onTranslationCompleted:" + mActivity + "is destroyed.");
             return;
@@ -369,7 +371,7 @@
                 }
                 final LongSparseArray<ViewTranslationResponse> virtualChildResponse =
                         translatedResult.valueAt(i);
-                if (DEBUG) {
+                if (isLoggable) {
                     Log.v(TAG, "onVirtualViewTranslationCompleted: received response for "
                             + "AutofillId " + autofillId);
                 }
@@ -379,7 +381,7 @@
                 }
                 mActivity.runOnUiThread(() -> {
                     if (view.getViewTranslationCallback() == null) {
-                        if (DEBUG) {
+                        if (isLoggable) {
                             Log.d(TAG, view + " doesn't support showing translation because of "
                                     + "null ViewTranslationCallback.");
                         }
@@ -397,12 +399,13 @@
      * The method is used to handle the translation result for non-vertual views.
      */
     private void onTranslationCompleted(SparseArray<ViewTranslationResponse> translatedResult) {
+        boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
         if (mActivity.isDestroyed()) {
             Log.v(TAG, "onTranslationCompleted:" + mActivity + "is destroyed.");
             return;
         }
         final int resultCount = translatedResult.size();
-        if (DEBUG) {
+        if (isLoggable) {
             Log.v(TAG, "onTranslationCompleted: receive " + resultCount + " responses.");
         }
         synchronized (mLock) {
@@ -413,7 +416,7 @@
             }
             for (int i = 0; i < resultCount; i++) {
                 final ViewTranslationResponse response = translatedResult.valueAt(i);
-                if (DEBUG) {
+                if (isLoggable) {
                     Log.v(TAG, "onTranslationCompleted: "
                             + sanitizedViewTranslationResponse(response));
                 }
@@ -443,7 +446,7 @@
                                     (TextViewTranslationCallback) callback;
                             if (textViewCallback.isShowingTranslation()
                                     || textViewCallback.isAnimationRunning()) {
-                                if (DEBUG) {
+                                if (isLoggable) {
                                     Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
                                             + ". Ignoring.");
                                 }
@@ -458,7 +461,7 @@
                             callback = new TextViewTranslationCallback();
                             view.setViewTranslationCallback(callback);
                         } else {
-                            if (DEBUG) {
+                            if (isLoggable) {
                                 Log.d(TAG, view + " doesn't support showing translation because of "
                                         + "null ViewTranslationCallback.");
                             }
@@ -506,7 +509,7 @@
         final TranslationRequest request = new TranslationRequest.Builder()
                 .setViewTranslationRequests(requests)
                 .build();
-        if (DEBUG) {
+        if (Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)) {
             StringBuilder msg = new StringBuilder("sendTranslationRequest:{requests=[");
             for (ViewTranslationRequest viewRequest: requests) {
                 msg.append("{request=")
@@ -636,6 +639,7 @@
 
     private void runForEachView(BiConsumer<View, ViewTranslationCallback> action) {
         synchronized (mLock) {
+            boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
             final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews);
             if (views.size() == 0) {
                 Log.w(TAG, "No views can be excuted for runForEachView.");
@@ -644,12 +648,12 @@
                 final int viewCounts = views.size();
                 for (int i = 0; i < viewCounts; i++) {
                     final View view = views.valueAt(i).get();
-                    if (DEBUG) {
+                    if (isLoggable) {
                         Log.d(TAG, "runForEachView for autofillId = " + (view != null
                                 ? view.getAutofillId() : " null"));
                     }
                     if (view == null || view.getViewTranslationCallback() == null) {
-                        if (DEBUG) {
+                        if (isLoggable) {
                             Log.d(TAG, "View was gone or ViewTranslationCallback for autofillId "
                                     + "= " + views.keyAt(i));
                         }
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 088065d2..7931d1a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -8095,12 +8095,14 @@
         private boolean mIsInsertModeActive;
         private InsertModeTransformationMethod mInsertModeTransformationMethod;
         private final Paint mHighlightPaint;
+        private final Path mHighlightPath;
 
         InsertModeController(@NonNull TextView textView) {
             mTextView = Objects.requireNonNull(textView);
             mIsInsertModeActive = false;
             mInsertModeTransformationMethod = null;
             mHighlightPaint = new Paint();
+            mHighlightPath = new Path();
 
             // The highlight color is supposed to be 12% of the color primary40. We can't
             // directly access Material 3 theme. But because Material 3 sets the colorPrimary to
@@ -8168,10 +8170,8 @@
                         ((InsertModeTransformationMethod.TransformedText) transformedText);
                 final int highlightStart = insertModeTransformedText.getHighlightStart();
                 final int highlightEnd = insertModeTransformedText.getHighlightEnd();
-                final Layout.SelectionRectangleConsumer consumer =
-                        (left, top, right, bottom, textSelectionLayout) ->
-                                canvas.drawRect(left, top, right, bottom, mHighlightPaint);
-                layout.getSelection(highlightStart, highlightEnd, consumer);
+                layout.getSelectionPath(highlightStart, highlightEnd, mHighlightPath);
+                canvas.drawPath(mHighlightPath, mHighlightPaint);
             }
         }
 
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
index 8012a1c..c475723 100644
--- a/core/java/android/window/BackMotionEvent.java
+++ b/core/java/android/window/BackMotionEvent.java
@@ -34,6 +34,8 @@
     private final float mTouchX;
     private final float mTouchY;
     private final float mProgress;
+    private final float mVelocityX;
+    private final float mVelocityY;
 
     @BackEvent.SwipeEdge
     private final int mSwipeEdge;
@@ -43,19 +45,32 @@
     /**
      * Creates a new {@link BackMotionEvent} instance.
      *
+     * <p>Note: Velocity is only computed for last event, for performance reasons.</p>
+     *
      * @param touchX Absolute X location of the touch point of this event.
      * @param touchY Absolute Y location of the touch point of this event.
      * @param progress Value between 0 and 1 on how far along the back gesture is.
+     * @param velocityX X velocity computed from the touch point of this event.
+     *                  Value in pixels/second. {@link Float#NaN} if was not computed.
+     * @param velocityY Y velocity computed from the touch point of this event.
+     *                  Value in pixels/second. {@link Float#NaN} if was not computed.
      * @param swipeEdge Indicates which edge the swipe starts from.
      * @param departingAnimationTarget The remote animation target of the departing
      *                                 application window.
      */
-    public BackMotionEvent(float touchX, float touchY, float progress,
+    public BackMotionEvent(
+            float touchX,
+            float touchY,
+            float progress,
+            float velocityX,
+            float velocityY,
             @BackEvent.SwipeEdge int swipeEdge,
             @Nullable RemoteAnimationTarget departingAnimationTarget) {
         mTouchX = touchX;
         mTouchY = touchY;
         mProgress = progress;
+        mVelocityX = velocityX;
+        mVelocityY = velocityY;
         mSwipeEdge = swipeEdge;
         mDepartingAnimationTarget = departingAnimationTarget;
     }
@@ -64,6 +79,8 @@
         mTouchX = in.readFloat();
         mTouchY = in.readFloat();
         mProgress = in.readFloat();
+        mVelocityX = in.readFloat();
+        mVelocityY = in.readFloat();
         mSwipeEdge = in.readInt();
         mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
     }
@@ -91,21 +108,13 @@
         dest.writeFloat(mTouchX);
         dest.writeFloat(mTouchY);
         dest.writeFloat(mProgress);
+        dest.writeFloat(mVelocityX);
+        dest.writeFloat(mVelocityY);
         dest.writeInt(mSwipeEdge);
         dest.writeTypedObject(mDepartingAnimationTarget, flags);
     }
 
     /**
-     * Returns the progress of a {@link BackEvent}.
-     *
-     * @see BackEvent#getProgress()
-     */
-    @FloatRange(from = 0, to = 1)
-    public float getProgress() {
-        return mProgress;
-    }
-
-    /**
      * Returns the absolute X location of the touch point.
      */
     public float getTouchX() {
@@ -120,6 +129,34 @@
     }
 
     /**
+     * Returns the progress of a {@link BackEvent}.
+     *
+     * @see BackEvent#getProgress()
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getProgress() {
+        return mProgress;
+    }
+
+    /**
+     * Returns the X velocity computed from the touch point.
+     *
+     * @return value in pixels/second or {@link Float#NaN} if was not computed.
+     */
+    public float getVelocityX() {
+        return mVelocityX;
+    }
+
+    /**
+     * Returns the Y velocity computed from the touch point.
+     *
+     * @return value in pixels/second or {@link Float#NaN} if was not computed.
+     */
+    public float getVelocityY() {
+        return mVelocityY;
+    }
+
+    /**
      * Returns the screen edge that the swipe starts from.
      */
     @BackEvent.SwipeEdge
@@ -143,6 +180,8 @@
                 + "mTouchX=" + mTouchX
                 + ", mTouchY=" + mTouchY
                 + ", mProgress=" + mProgress
+                + ", mVelocityX=" + mVelocityX
+                + ", mVelocityY=" + mVelocityY
                 + ", mSwipeEdge" + mSwipeEdge
                 + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
                 + "}";
diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
index ad0d1a4..3801188 100644
--- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
+++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
@@ -20,6 +20,7 @@
 import android.service.voice.HotwordDetectedResult;
 import android.service.voice.HotwordDetectionServiceFailure;
 import android.service.voice.HotwordRejectedResult;
+import android.service.voice.SoundTriggerFailure;
 import android.service.voice.VisualQueryDetectionServiceFailure;
 
 /**
@@ -57,13 +58,6 @@
     void onRejected(in HotwordRejectedResult result);
 
     /**
-     * Called when the detection fails due to an error.
-     *
-     * @param status The error code that was seen.
-     */
-    void onError(int status);
-
-    /**
      * Called when the detection fails due to an error occurs in the
      * {@link HotwordDetectionService}.
      *
@@ -84,6 +78,15 @@
         in VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure);
 
     /**
+     * Called when the detection fails due to an error occurs in the
+     * {@link com.android.server.soundtrigger.SoundTriggerService}.
+     *
+     * @param soundTriggerFailure It provides the error code, error message and
+     *                                           suggested action.
+     */
+    void onSoundTriggerFailure(in SoundTriggerFailure soundTriggerFailure);
+
+    /**
      * Called when the detection fails due to an unknown error occurs.
      *
      * @param errorMessage It provides the error message.
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index ab7f602..ed751cb 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -16,8 +16,9 @@
 
 package com.android.internal.app;
 
-import android.media.permission.Identity;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.media.permission.Identity;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
 import com.android.internal.app.ISoundTriggerSession;
 
 /**
@@ -74,4 +75,8 @@
      */
     List<SoundTrigger.ModuleProperties> listModuleProperties(in Identity originatorIdentity);
 
+    /**
+     * Attach an HAL injection interface.
+     */
+     void attachInjection(ISoundTriggerInjection injection);
 }
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 6b40d98..24d5afc 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -96,6 +96,21 @@
      * @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES
      */
     int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
+
+    /**
+     * Override the persistent enrolled model database with an in-memory
+     * fake for testing purposes.
+     *
+     * @param enabled - {@code true} to enable the test database. {@code false} to enable
+     * the real, persistent database.
+     * @param token - IBinder used to register a death listener to clean-up the override
+     * if tests do not clean up gracefully.
+     */
+    @EnforcePermission("MANAGE_VOICE_KEYPHRASES")
+    @JavaPassthrough(annotation= "@android.annotation.RequiresPermission(" +
+            "android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)")
+    void setModelDatabaseForTestEnabled(boolean enabled, IBinder token);
+
     /**
      * Indicates if there's a keyphrase sound model available for the given keyphrase ID and the
      * user ID of the caller.
@@ -106,6 +121,7 @@
      * @param bcp47Locale The BCP47 language tag  for the keyphrase's locale.
      */
     boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale);
+
     /**
      * Generates KeyphraseMetadata for an enrolled sound model based on keyphrase string, locale,
      * and the user ID of the caller.
diff --git a/core/java/com/android/internal/expresslog/Counter.java b/core/java/com/android/internal/expresslog/Counter.java
deleted file mode 100644
index 4a46d91..0000000
--- a/core/java/com/android/internal/expresslog/Counter.java
+++ /dev/null
@@ -1,71 +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.internal.expresslog;
-
-import android.annotation.NonNull;
-
-import com.android.internal.util.FrameworkStatsLog;
-
-/** Counter encapsulates StatsD write API calls */
-public final class Counter {
-
-    // Not instantiable.
-    private Counter() {}
-
-    /**
-     * Increments Telemetry Express Counter metric by 1
-     * @param metricId to log, no-op if metricId is not defined in the TeX catalog
-     * @hide
-     */
-    public static void logIncrement(@NonNull String metricId) {
-        logIncrement(metricId, 1);
-    }
-
-    /**
-     * Increments Telemetry Express Counter metric by 1
-     * @param metricId to log, no-op if metricId is not defined in the TeX catalog
-     * @param uid used as a dimension for the count metric
-     * @hide
-     */
-    public static void logIncrementWithUid(@NonNull String metricId, int uid) {
-        logIncrementWithUid(metricId, uid, 1);
-    }
-
-    /**
-     * Increments Telemetry Express Counter metric by arbitrary value
-     * @param metricId to log, no-op if metricId is not defined in the TeX catalog
-     * @param amount to increment counter
-     * @hide
-     */
-    public static void logIncrement(@NonNull String metricId, long amount) {
-        final long metricIdHash = Utils.hashString(metricId);
-        FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount);
-    }
-
-    /**
-     * Increments Telemetry Express Counter metric by arbitrary value
-     * @param metricId to log, no-op if metricId is not defined in the TeX catalog
-     * @param uid used as a dimension for the count metric
-     * @param amount to increment counter
-     * @hide
-     */
-    public static void logIncrementWithUid(@NonNull String metricId, int uid, long amount) {
-        final long metricIdHash = Utils.hashString(metricId);
-        FrameworkStatsLog.write(
-                FrameworkStatsLog.EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid);
-    }
-}
diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java
deleted file mode 100644
index 2fe784a..0000000
--- a/core/java/com/android/internal/expresslog/Histogram.java
+++ /dev/null
@@ -1,234 +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.internal.expresslog;
-
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-
-import com.android.internal.util.FrameworkStatsLog;
-
-import java.util.Arrays;
-
-/** Histogram encapsulates StatsD write API calls */
-public final class Histogram {
-
-    private final long mMetricIdHash;
-    private final BinOptions mBinOptions;
-
-    /**
-     * Creates Histogram metric logging wrapper
-     *
-     * @param metricId   to log, logging will be no-op if metricId is not defined in the TeX catalog
-     * @param binOptions to calculate bin index for samples
-     * @hide
-     */
-    public Histogram(@NonNull String metricId, @NonNull BinOptions binOptions) {
-        mMetricIdHash = Utils.hashString(metricId);
-        mBinOptions = binOptions;
-    }
-
-    /**
-     * Logs increment sample count for automatically calculated bin
-     *
-     * @param sample value
-     * @hide
-     */
-    public void logSample(float sample) {
-        final int binIndex = mBinOptions.getBinForSample(sample);
-        FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash,
-                /*count*/ 1, binIndex);
-    }
-
-    /**
-     * Logs increment sample count for automatically calculated bin
-     *
-     * @param uid used as a dimension for the count metric
-     * @param sample value
-     * @hide
-     */
-    public void logSampleWithUid(int uid, float sample) {
-        final int binIndex = mBinOptions.getBinForSample(sample);
-        FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED,
-                mMetricIdHash, /*count*/ 1, binIndex, uid);
-    }
-
-    /** Used by Histogram to map data sample to corresponding bin */
-    public interface BinOptions {
-        /**
-         * Returns bins count to be used by a histogram
-         *
-         * @return bins count used to initialize Options, including overflow & underflow bins
-         * @hide
-         */
-        int getBinsCount();
-
-        /**
-         * Returns bin index for the input sample value
-         * index == 0 stands for underflow
-         * index == getBinsCount() - 1 stands for overflow
-         *
-         * @return zero based index
-         * @hide
-         */
-        int getBinForSample(float sample);
-    }
-
-    /** Used by Histogram to map data sample to corresponding bin for uniform bins */
-    public static final class UniformOptions implements BinOptions {
-
-        private final int mBinCount;
-        private final float mMinValue;
-        private final float mExclusiveMaxValue;
-        private final float mBinSize;
-
-        /**
-         * Creates options for uniform (linear) sized bins
-         *
-         * @param binCount          amount of histogram bins. 2 bin indexes will be calculated
-         *                          automatically to represent underflow & overflow bins
-         * @param minValue          is included in the first bin, values less than minValue
-         *                          go to underflow bin
-         * @param exclusiveMaxValue is included in the overflow bucket. For accurate
-         *                          measure up to kMax, then exclusiveMaxValue
-         *                          should be set to kMax + 1
-         * @hide
-         */
-        public UniformOptions(@IntRange(from = 1) int binCount, float minValue,
-                float exclusiveMaxValue) {
-            if (binCount < 1) {
-                throw new IllegalArgumentException("Bin count should be positive number");
-            }
-
-            if (exclusiveMaxValue <= minValue) {
-                throw new IllegalArgumentException("Bins range invalid (maxValue < minValue)");
-            }
-
-            mMinValue = minValue;
-            mExclusiveMaxValue = exclusiveMaxValue;
-            mBinSize = (mExclusiveMaxValue - minValue) / binCount;
-
-            // Implicitly add 2 for the extra underflow & overflow bins
-            mBinCount = binCount + 2;
-        }
-
-        @Override
-        public int getBinsCount() {
-            return mBinCount;
-        }
-
-        @Override
-        public int getBinForSample(float sample) {
-            if (sample < mMinValue) {
-                // goes to underflow
-                return 0;
-            } else if (sample >= mExclusiveMaxValue) {
-                // goes to overflow
-                return mBinCount - 1;
-            }
-            return (int) ((sample - mMinValue) / mBinSize + 1);
-        }
-    }
-
-    /** Used by Histogram to map data sample to corresponding bin for scaled bins */
-    public static final class ScaledRangeOptions implements BinOptions {
-        // store minimum value per bin
-        final long[] mBins;
-
-        /**
-         * Creates options for scaled range bins
-         *
-         * @param binCount      amount of histogram bins. 2 bin indexes will be calculated
-         *                      automatically to represent underflow & overflow bins
-         * @param minValue      is included in the first bin, values less than minValue
-         *                      go to underflow bin
-         * @param firstBinWidth used to represent first bin width and as a reference to calculate
-         *                      width for consecutive bins
-         * @param scaleFactor   used to calculate width for consecutive bins
-         * @hide
-         */
-        public ScaledRangeOptions(@IntRange(from = 1) int binCount, int minValue,
-                @FloatRange(from = 1.f) float firstBinWidth,
-                @FloatRange(from = 1.f) float scaleFactor) {
-            if (binCount < 1) {
-                throw new IllegalArgumentException("Bin count should be positive number");
-            }
-
-            if (firstBinWidth < 1.f) {
-                throw new IllegalArgumentException(
-                        "First bin width invalid (should be 1.f at minimum)");
-            }
-
-            if (scaleFactor < 1.f) {
-                throw new IllegalArgumentException(
-                        "Scaled factor invalid (should be 1.f at minimum)");
-            }
-
-            // precalculating bins ranges (no need to create a bin for underflow reference value)
-            mBins = initBins(binCount + 1, minValue, firstBinWidth, scaleFactor);
-        }
-
-        @Override
-        public int getBinsCount() {
-            return mBins.length + 1;
-        }
-
-        @Override
-        public int getBinForSample(float sample) {
-            if (sample < mBins[0]) {
-                // goes to underflow
-                return 0;
-            } else if (sample >= mBins[mBins.length - 1]) {
-                // goes to overflow
-                return mBins.length;
-            }
-
-            return lower_bound(mBins, (long) sample) + 1;
-        }
-
-        // To find lower bound using binary search implementation of Arrays utility class
-        private static int lower_bound(long[] array, long sample) {
-            int index = Arrays.binarySearch(array, sample);
-            // If key is not present in the array
-            if (index < 0) {
-                // Index specify the position of the key when inserted in the sorted array
-                // so the element currently present at this position will be the lower bound
-                return Math.abs(index) - 2;
-            }
-            return index;
-        }
-
-        private static long[] initBins(int count, int minValue, float firstBinWidth,
-                float scaleFactor) {
-            long[] bins = new long[count];
-            bins[0] = minValue;
-            double lastWidth = firstBinWidth;
-            for (int i = 1; i < count; i++) {
-                // current bin minValue = previous bin width * scaleFactor
-                double currentBinMinValue = bins[i - 1] + lastWidth;
-                if (currentBinMinValue > Integer.MAX_VALUE) {
-                    throw new IllegalArgumentException(
-                        "Attempted to create a bucket larger than maxint");
-                }
-
-                bins[i] = (long) currentBinMinValue;
-                lastWidth *= scaleFactor;
-            }
-            return bins;
-        }
-    }
-}
diff --git a/core/java/com/android/internal/expresslog/OWNERS b/core/java/com/android/internal/expresslog/OWNERS
deleted file mode 100644
index ee865b1..0000000
--- a/core/java/com/android/internal/expresslog/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/stats/OWNERS
diff --git a/core/java/com/android/internal/expresslog/TEST_MAPPING b/core/java/com/android/internal/expresslog/TEST_MAPPING
deleted file mode 100644
index c9b0cf8..0000000
--- a/core/java/com/android/internal/expresslog/TEST_MAPPING
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "ExpressLogTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
index e2c096c1..aa6b1c0 100644
--- a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
@@ -20,7 +20,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
 
 import java.io.IOException;
 import java.util.Arrays;
diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java
index f6d80a5..8fe2b9c 100644
--- a/core/java/com/android/internal/util/DumpUtils.java
+++ b/core/java/com/android/internal/util/DumpUtils.java
@@ -25,6 +25,7 @@
 import android.os.Handler;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -312,5 +313,85 @@
                     || cn.flattenToString().toLowerCase().contains(filterString.toLowerCase());
         };
     }
-}
 
+    /**
+     * Lambda used to dump a key (and its index) while iterating though a collection.
+     */
+    public interface KeyDumper {
+
+        /** Dumps the index and key.*/
+        void dump(int index, int key);
+    }
+
+    /**
+     * Lambda used to dump a value while iterating though a collection.
+     *
+     * @param <T> type of the value.
+     */
+    public interface ValueDumper<T> {
+
+        /** Dumps the value.*/
+        void dump(T value);
+    }
+
+    /**
+     * Dumps a sparse array.
+     */
+    public static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array,
+            String name) {
+        dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valueDumper= */ null);
+    }
+
+    /**
+     * Dumps the values of a sparse array.
+     */
+    public static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix,
+            SparseArray<T> array, String name) {
+        dumpSparseArray(pw, prefix, array, name, (i, k) -> {
+            pw.printf("%s%s", prefix, prefix);
+        }, /* valueDumper= */ null);
+    }
+
+    /**
+     * Dumps a sparse array, customizing each line.
+     */
+    public static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array,
+            String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) {
+        int size = array.size();
+        if (size == 0) {
+            pw.print(prefix);
+            pw.print("No ");
+            pw.print(name);
+            pw.println("s");
+            return;
+        }
+        pw.print(prefix);
+        pw.print(size);
+        pw.print(' ');
+        pw.print(name);
+        pw.println("(s):");
+
+        String prefix2 = prefix + prefix;
+        for (int i = 0; i < size; i++) {
+            int key = array.keyAt(i);
+            T value = array.valueAt(i);
+            if (keyDumper != null) {
+                keyDumper.dump(i, key);
+            } else {
+                pw.print(prefix2);
+                pw.print(i);
+                pw.print(": ");
+                pw.print(key);
+                pw.print("->");
+            }
+            if (value == null) {
+                pw.print("(null)");
+            } else if (valueDumper != null) {
+                valueDumper.dump(value);
+            } else {
+                pw.print(value);
+            }
+            pw.println();
+        }
+    }
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index fc26766..6bec6bc 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -225,7 +225,6 @@
                 "android_security_Scrypt.cpp",
                 "com_android_internal_content_om_OverlayConfig.cpp",
                 "com_android_internal_content_om_OverlayManagerImpl.cpp",
-                "com_android_internal_expresslog_Utils.cpp",
                 "com_android_internal_net_NetworkUtilsInternal.cpp",
                 "com_android_internal_os_ClassLoaderFactory.cpp",
                 "com_android_internal_os_FuseAppLoop.cpp",
@@ -262,6 +261,7 @@
                 "libstatssocket_lazy",
                 "libskia",
                 "libtextclassifier_hash_static",
+                "libexpresslog_jni",
             ],
 
             shared_libs: [
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b550f28..21bdf09 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -200,7 +200,7 @@
 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_internal_expresslog_Utils(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);
@@ -1586,7 +1586,7 @@
         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_internal_expresslog_Utils),
+        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/OWNERS b/core/jni/OWNERS
index bce53328..4e4abec 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -104,6 +104,3 @@
 
 # PM
 per-file com_android_internal_content_* = file:/PACKAGE_MANAGER_OWNERS
-
-# Stats/expresslog
-per-file *expresslog* = file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 8ba4eed..e1be0cd 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2481,36 +2481,31 @@
 
     if (jSurroundFormats == nullptr) {
         ALOGE("jSurroundFormats is NULL");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
     if (!env->IsInstanceOf(jSurroundFormats, gMapClass)) {
         ALOGE("getSurroundFormats not a map");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
 
     jint jStatus;
     unsigned int numSurroundFormats = 0;
-    audio_format_t *surroundFormats = nullptr;
-    bool *surroundFormatsEnabled = nullptr;
-    status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats,
-                                                      surroundFormatsEnabled);
+    status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, nullptr, nullptr);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status);
-        jStatus = nativeToJavaStatus(status);
-        goto exit;
+        return nativeToJavaStatus(status);
     }
     if (numSurroundFormats == 0) {
-        jStatus = (jint)AUDIO_JAVA_SUCCESS;
-        goto exit;
+        return static_cast<jint>(AUDIO_JAVA_SUCCESS);
     }
-    surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t));
-    surroundFormatsEnabled = (bool *)calloc(numSurroundFormats, sizeof(bool));
-    status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats,
-                                             surroundFormatsEnabled);
+    auto surroundFormats = std::make_unique<audio_format_t[]>(numSurroundFormats);
+    auto surroundFormatsEnabled = std::make_unique<bool[]>(numSurroundFormats);
+    status = AudioSystem::getSurroundFormats(&numSurroundFormats, &surroundFormats[0],
+                                             &surroundFormatsEnabled[0]);
     jStatus = nativeToJavaStatus(status);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status);
-        goto exit;
+        return jStatus;
     }
     for (size_t i = 0; i < numSurroundFormats; i++) {
         int audioFormat = audioFormatFromNative(surroundFormats[i]);
@@ -2526,9 +2521,6 @@
         env->DeleteLocalRef(enabled);
     }
 
-exit:
-    free(surroundFormats);
-    free(surroundFormatsEnabled);
     return jStatus;
 }
 
@@ -2538,31 +2530,28 @@
 
     if (jSurroundFormats == nullptr) {
         ALOGE("jSurroundFormats is NULL");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
     if (!env->IsInstanceOf(jSurroundFormats, gArrayListClass)) {
         ALOGE("jSurroundFormats not an arraylist");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
     jint jStatus;
     unsigned int numSurroundFormats = 0;
-    audio_format_t *surroundFormats = nullptr;
-    status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats);
+    status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, nullptr);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status);
-        jStatus = nativeToJavaStatus(status);
-        goto exit;
+        return nativeToJavaStatus(status);
     }
     if (numSurroundFormats == 0) {
-        jStatus = (jint)AUDIO_JAVA_SUCCESS;
-        goto exit;
+        return static_cast<jint>(AUDIO_JAVA_SUCCESS);
     }
-    surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t));
-    status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats);
+    auto surroundFormats = std::make_unique<audio_format_t[]>(numSurroundFormats);
+    status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, &surroundFormats[0]);
     jStatus = nativeToJavaStatus(status);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status);
-        goto exit;
+        return jStatus;
     }
     for (size_t i = 0; i < numSurroundFormats; i++) {
         int audioFormat = audioFormatFromNative(surroundFormats[i]);
@@ -2576,8 +2565,6 @@
         env->DeleteLocalRef(surroundFormat);
     }
 
-exit:
-    free(surroundFormats);
     return jStatus;
 }
 
diff --git a/core/jni/com_android_internal_expresslog_Utils.cpp b/core/jni/com_android_internal_expresslog_Utils.cpp
deleted file mode 100644
index d33a7bd..0000000
--- a/core/jni/com_android_internal_expresslog_Utils.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <nativehelper/JNIHelp.h>
-#include <utils/hash/farmhash.h>
-
-#include "core_jni_helpers.h"
-
-// ----------------------------------------------------------------------------
-// JNI Glue
-// ----------------------------------------------------------------------------
-
-static jclass g_stringClass = nullptr;
-
-/**
- * Class:     com_android_internal_expresslog_Utils
- * Method:    hashString
- * Signature: (Ljava/lang/String;)J
- */
-static jlong hashString(JNIEnv* env, jclass /*class*/, jstring metricNameObj) {
-    ScopedUtfChars name(env, metricNameObj);
-    if (name.c_str() == nullptr) {
-        return 0;
-    }
-
-    return static_cast<jlong>(farmhash::Fingerprint64(name.c_str(), name.size()));
-}
-
-static const JNINativeMethod g_methods[] = {
-        {"hashString", "(Ljava/lang/String;)J", (void*)hashString},
-};
-
-static const char* const kUtilsPathName = "com/android/internal/expresslog/Utils";
-
-namespace android {
-
-int register_com_android_internal_expresslog_Utils(JNIEnv* env) {
-    jclass stringClass = FindClassOrDie(env, "java/lang/String");
-    g_stringClass = MakeGlobalRefOrDie(env, stringClass);
-
-    return RegisterMethodsOrDie(env, kUtilsPathName, g_methods, NELEM(g_methods));
-}
-
-} // namespace android
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index ed612a0..025a57d 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -1035,6 +1035,7 @@
 
             optional int32 uid = 1;
             repeated .android.app.ApplicationExitInfoProto app_exit_info = 2;
+            repeated .android.app.ApplicationExitInfoProto app_recoverable_crash = 3;
         }
         repeated User users = 2;
     }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 78d3923..05b38a5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1746,37 +1746,6 @@
         android:protectionLevel="dangerous"
         android:permissionFlags="hardRestricted" />
 
-    <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors.
-        <p class="note"><strong>Note: </strong> This permission is for Wear OS only.
-        <p>Protection level: dangerous
-        @hide
-        -->
-    <permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE"
-                android:permissionGroup="android.permission-group.UNDEFINED"
-                android:label="@string/permlab_bodySensorsWristTemperature"
-                android:description="@string/permdesc_bodySensorsWristTemperature"
-                android:backgroundPermission="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"
-                android:protectionLevel="dangerous" />
-
-    <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors.
-         If you're requesting this permission, you must also request
-         {@link #BODY_SENSORS_WRIST_TEMPERATURE}. Requesting this permission by itself doesn't
-         give you wrist temperature body sensors access.
-         <p class="note"><strong>Note: </strong> This permission is for Wear OS only.
-         <p>Protection level: dangerous
-
-         <p> This is a hard restricted permission which cannot be held by an app until
-         the installer on record allowlists the permission. For more details see
-         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
-         @hide
-    -->
-    <permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"
-                android:permissionGroup="android.permission-group.UNDEFINED"
-                android:label="@string/permlab_bodySensors_wristTemperature_background"
-                android:description="@string/permdesc_bodySensors_wristTemperature_background"
-                android:protectionLevel="dangerous"
-                android:permissionFlags="hardRestricted" />
-
     <!-- Allows an app to use fingerprint hardware.
          <p>Protection level: normal
          @deprecated Applications should request {@link
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e4d74b5..9f10ae60 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1373,6 +1373,9 @@
     <!-- Number of notifications to keep in the notification service historical archive -->
     <integer name="config_notificationServiceArchiveSize">100</integer>
 
+    <!-- List of packages that will be able to use full screen intent in notifications by default -->
+    <string-array name="config_useFullScreenIntentPackages" translatable="false" />
+
     <!-- Allow the menu hard key to be disabled in LockScreen on some devices -->
     <bool name="config_disableMenuKeyInLockScreen">false</bool>
 
diff --git a/core/res/res/values/public-final.xml b/core/res/res/values/public-final.xml
index 85325fe..daa0f553 100644
--- a/core/res/res/values/public-final.xml
+++ b/core/res/res/values/public-final.xml
@@ -3402,4 +3402,343 @@
     <!-- @hide @SystemApi -->
   <public type="bool" name="config_enableQrCodeScannerOnLockScreen" id="0x01110008" />
 
+  <!-- ===============================================================
+    Resources added in version U of the platform
+
+    NOTE: After this version of the platform is forked, changes cannot be made to the root
+    branch's groups for that release. Only merge changes to the forked platform branch.
+    =============================================================== -->
+  <eat-comment/>
+
+  <staging-public-group-final type="attr" first-id="0x01ce0000">
+    <public name="handwritingBoundsOffsetLeft" />
+    <public name="handwritingBoundsOffsetTop" />
+    <public name="handwritingBoundsOffsetRight" />
+    <public name="handwritingBoundsOffsetBottom" />
+    <public name="accessibilityDataSensitive" />
+    <public name="enableTextStylingShortcuts" />
+    <public name="requiredDisplayCategory"/>
+    <public name="removed_maxConcurrentSessionsCount" />
+    <public name="visualQueryDetectionService" />
+    <public name="physicalKeyboardHintLanguageTag" />
+    <public name="physicalKeyboardHintLayoutType" />
+    <public name="allowSharedIsolatedProcess" />
+    <public name="keyboardLocale" />
+    <public name="keyboardLayoutType" />
+    <public name="allowUpdateOwnership" />
+    <public name="isCredential"/>
+    <public name="searchResultHighlightColor" />
+    <public name="focusedSearchResultHighlightColor" />
+    <public name="stylusHandwritingSettingsActivity" />
+    <public name="windowNoMoveAnimation" />
+    <public name="settingsSubtitle" />
+    <public name="capability" />
+  </staging-public-group-final>
+
+  <public type="attr" name="handwritingBoundsOffsetLeft" id="0x01010673" />
+  <public type="attr" name="handwritingBoundsOffsetTop" id="0x01010674" />
+  <public type="attr" name="handwritingBoundsOffsetRight" id="0x01010675" />
+  <public type="attr" name="handwritingBoundsOffsetBottom" id="0x01010676" />
+  <public type="attr" name="accessibilityDataSensitive" id="0x01010677" />
+  <public type="attr" name="enableTextStylingShortcuts" id="0x01010678" />
+  <public type="attr" name="requiredDisplayCategory" id="0x01010679" />
+  <public type="attr" name="visualQueryDetectionService" id="0x0101067a" />
+  <public type="attr" name="physicalKeyboardHintLanguageTag" id="0x0101067b" />
+  <public type="attr" name="physicalKeyboardHintLayoutType" id="0x0101067c" />
+  <public type="attr" name="allowSharedIsolatedProcess" id="0x0101067d" />
+  <public type="attr" name="keyboardLocale" id="0x0101067e" />
+  <public type="attr" name="keyboardLayoutType" id="0x0101067f" />
+  <public type="attr" name="allowUpdateOwnership" id="0x01010680" />
+  <public type="attr" name="isCredential" id="0x01010681" />
+  <public type="attr" name="searchResultHighlightColor" id="0x01010682" />
+  <public type="attr" name="focusedSearchResultHighlightColor" id="0x01010683" />
+  <public type="attr" name="stylusHandwritingSettingsActivity" id="0x01010684" />
+  <public type="attr" name="windowNoMoveAnimation" id="0x01010685" />
+  <public type="attr" name="settingsSubtitle" id="0x01010686" />
+  <public type="attr" name="capability" id="0x01010687" />
+
+  <staging-public-group-final type="id" first-id="0x01cd0000">
+    <public name="bold" />
+    <public name="italic" />
+    <public name="underline" />
+    <public name="accessibilityActionScrollInDirection" />
+  </staging-public-group-final>
+
+  <public type="id" name="bold" id="0x0102005b" />
+  <public type="id" name="italic" id="0x0102005c" />
+  <public type="id" name="underline" id="0x0102005d" />
+  <public type="id" name="accessibilityActionScrollInDirection" id="0x0102005e" />
+
+  <staging-public-group-final type="string" first-id="0x01cb0000">
+    <!-- @hide @SystemApi -->
+    <public name="config_systemWearHealthService" />
+    <!-- @hide @SystemApi -->
+    <public name="config_defaultNotes" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemFinancedDeviceController" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemCallStreaming" />
+  </staging-public-group-final>
+
+    <!-- @hide @SystemApi -->
+  <public type="string" name="config_systemWearHealthService" id="0x01040044" />
+    <!-- @hide @SystemApi -->
+  <public type="string" name="config_defaultNotes" id="0x01040045" />
+    <!-- @hide @SystemApi -->
+  <public type="string" name="config_systemFinancedDeviceController" id="0x01040046" />
+    <!-- @hide @SystemApi -->
+  <public type="string" name="config_systemCallStreaming" id="0x01040047" />
+
+  <staging-public-group-final type="dimen" first-id="0x01ca0000">
+    <!-- @hide @SystemApi -->
+    <public name="config_viewConfigurationHandwritingGestureLineMargin" />
+  </staging-public-group-final>
+
+    <!-- @hide @SystemApi -->
+  <public type="dimen" name="config_viewConfigurationHandwritingGestureLineMargin" id="0x0105000a" />
+
+  <staging-public-group-final type="color" first-id="0x01c90000">
+    <public name="system_primary_container_light" />
+    <public name="system_on_primary_container_light" />
+    <public name="system_primary_light" />
+    <public name="system_on_primary_light" />
+    <public name="system_secondary_container_light" />
+    <public name="system_on_secondary_container_light" />
+    <public name="system_secondary_light" />
+    <public name="system_on_secondary_light" />
+    <public name="system_tertiary_container_light" />
+    <public name="system_on_tertiary_container_light" />
+    <public name="system_tertiary_light" />
+    <public name="system_on_tertiary_light" />
+    <public name="system_background_light" />
+    <public name="system_on_background_light" />
+    <public name="system_surface_light" />
+    <public name="system_on_surface_light" />
+    <public name="system_surface_container_low_light" />
+    <public name="system_surface_container_lowest_light" />
+    <public name="system_surface_container_light" />
+    <public name="system_surface_container_high_light" />
+    <public name="system_surface_container_highest_light" />
+    <public name="system_surface_bright_light" />
+    <public name="system_surface_dim_light" />
+    <public name="system_surface_variant_light" />
+    <public name="system_on_surface_variant_light" />
+    <public name="system_outline_light" />
+    <public name="system_error_light" />
+    <public name="system_on_error_light" />
+    <public name="system_error_container_light" />
+    <public name="system_on_error_container_light" />
+    <public name="removed_system_primary_fixed_light" />
+    <public name="removed_system_primary_fixed_dim_light" />
+    <public name="removed_system_on_primary_fixed_light" />
+    <public name="removed_system_on_primary_fixed_variant_light" />
+    <public name="removed_system_secondary_fixed_light" />
+    <public name="removed_system_secondary_fixed_dim_light" />
+    <public name="removed_system_on_secondary_fixed_light" />
+    <public name="removed_system_on_secondary_fixed_variant_light" />
+    <public name="removed_system_tertiary_fixed_light" />
+    <public name="removed_system_tertiary_fixed_dim_light" />
+    <public name="removed_system_on_tertiary_fixed_light" />
+    <public name="removed_system_on_tertiary_fixed_variant_light" />
+    <public name="system_control_activated_light" />
+    <public name="system_control_normal_light" />
+    <public name="system_control_highlight_light" />
+    <public name="system_text_primary_inverse_light" />
+    <public name="system_text_secondary_and_tertiary_inverse_light" />
+    <public name="system_text_primary_inverse_disable_only_light" />
+    <public name="system_text_secondary_and_tertiary_inverse_disabled_light" />
+    <public name="system_text_hint_inverse_light" />
+    <public name="system_palette_key_color_primary_light" />
+    <public name="system_palette_key_color_secondary_light" />
+    <public name="system_palette_key_color_tertiary_light" />
+    <public name="system_palette_key_color_neutral_light" />
+    <public name="system_palette_key_color_neutral_variant_light" />
+    <public name="system_primary_container_dark"/>
+    <public name="system_on_primary_container_dark"/>
+    <public name="system_primary_dark"/>
+    <public name="system_on_primary_dark"/>
+    <public name="system_secondary_container_dark"/>
+    <public name="system_on_secondary_container_dark"/>
+    <public name="system_secondary_dark"/>
+    <public name="system_on_secondary_dark"/>
+    <public name="system_tertiary_container_dark"/>
+    <public name="system_on_tertiary_container_dark"/>
+    <public name="system_tertiary_dark"/>
+    <public name="system_on_tertiary_dark"/>
+    <public name="system_background_dark"/>
+    <public name="system_on_background_dark"/>
+    <public name="system_surface_dark"/>
+    <public name="system_on_surface_dark"/>
+    <public name="system_surface_container_low_dark"/>
+    <public name="system_surface_container_lowest_dark"/>
+    <public name="system_surface_container_dark"/>
+    <public name="system_surface_container_high_dark"/>
+    <public name="system_surface_container_highest_dark"/>
+    <public name="system_surface_bright_dark"/>
+    <public name="system_surface_dim_dark"/>
+    <public name="system_surface_variant_dark"/>
+    <public name="system_on_surface_variant_dark"/>
+    <public name="system_outline_dark"/>
+    <public name="system_error_dark"/>
+    <public name="system_on_error_dark"/>
+    <public name="system_error_container_dark"/>
+    <public name="system_on_error_container_dark"/>
+    <public name="removed_system_primary_fixed_dark"/>
+    <public name="removed_system_primary_fixed_dim_dark"/>
+    <public name="removed_system_on_primary_fixed_dark"/>
+    <public name="removed_system_on_primary_fixed_variant_dark"/>
+    <public name="removed_system_secondary_fixed_dark"/>
+    <public name="removed_system_secondary_fixed_dim_dark"/>
+    <public name="removed_system_on_secondary_fixed_dark"/>
+    <public name="removed_system_on_secondary_fixed_variant_dark"/>
+    <public name="removed_system_tertiary_fixed_dark"/>
+    <public name="removed_system_tertiary_fixed_dim_dark"/>
+    <public name="removed_system_on_tertiary_fixed_dark"/>
+    <public name="removed_system_on_tertiary_fixed_variant_dark"/>
+    <public name="system_control_activated_dark"/>
+    <public name="system_control_normal_dark"/>
+    <public name="system_control_highlight_dark"/>
+    <public name="system_text_primary_inverse_dark"/>
+    <public name="system_text_secondary_and_tertiary_inverse_dark"/>
+    <public name="system_text_primary_inverse_disable_only_dark"/>
+    <public name="system_text_secondary_and_tertiary_inverse_disabled_dark"/>
+    <public name="system_text_hint_inverse_dark"/>
+    <public name="system_palette_key_color_primary_dark"/>
+    <public name="system_palette_key_color_secondary_dark"/>
+    <public name="system_palette_key_color_tertiary_dark"/>
+    <public name="system_palette_key_color_neutral_dark"/>
+    <public name="system_palette_key_color_neutral_variant_dark"/>
+    <public name="system_primary_fixed" />
+    <public name="system_primary_fixed_dim" />
+    <public name="system_on_primary_fixed" />
+    <public name="system_on_primary_fixed_variant" />
+    <public name="system_secondary_fixed" />
+    <public name="system_secondary_fixed_dim" />
+    <public name="system_on_secondary_fixed" />
+    <public name="system_on_secondary_fixed_variant" />
+    <public name="system_tertiary_fixed" />
+    <public name="system_tertiary_fixed_dim" />
+    <public name="system_on_tertiary_fixed" />
+    <public name="system_on_tertiary_fixed_variant" />
+    <public name="system_outline_variant_light" />
+    <public name="system_outline_variant_dark" />
+  </staging-public-group-final>
+
+  <public type="color" name="system_primary_container_light" id="0x0106005e" />
+  <public type="color" name="system_on_primary_container_light" id="0x0106005f" />
+  <public type="color" name="system_primary_light" id="0x01060060" />
+  <public type="color" name="system_on_primary_light" id="0x01060061" />
+  <public type="color" name="system_secondary_container_light" id="0x01060062" />
+  <public type="color" name="system_on_secondary_container_light" id="0x01060063" />
+  <public type="color" name="system_secondary_light" id="0x01060064" />
+  <public type="color" name="system_on_secondary_light" id="0x01060065" />
+  <public type="color" name="system_tertiary_container_light" id="0x01060066" />
+  <public type="color" name="system_on_tertiary_container_light" id="0x01060067" />
+  <public type="color" name="system_tertiary_light" id="0x01060068" />
+  <public type="color" name="system_on_tertiary_light" id="0x01060069" />
+  <public type="color" name="system_background_light" id="0x0106006a" />
+  <public type="color" name="system_on_background_light" id="0x0106006b" />
+  <public type="color" name="system_surface_light" id="0x0106006c" />
+  <public type="color" name="system_on_surface_light" id="0x0106006d" />
+  <public type="color" name="system_surface_container_low_light" id="0x0106006e" />
+  <public type="color" name="system_surface_container_lowest_light" id="0x0106006f" />
+  <public type="color" name="system_surface_container_light" id="0x01060070" />
+  <public type="color" name="system_surface_container_high_light" id="0x01060071" />
+  <public type="color" name="system_surface_container_highest_light" id="0x01060072" />
+  <public type="color" name="system_surface_bright_light" id="0x01060073" />
+  <public type="color" name="system_surface_dim_light" id="0x01060074" />
+  <public type="color" name="system_surface_variant_light" id="0x01060075" />
+  <public type="color" name="system_on_surface_variant_light" id="0x01060076" />
+  <public type="color" name="system_outline_light" id="0x01060077" />
+  <public type="color" name="system_error_light" id="0x01060078" />
+  <public type="color" name="system_on_error_light" id="0x01060079" />
+  <public type="color" name="system_error_container_light" id="0x0106007a" />
+  <public type="color" name="system_on_error_container_light" id="0x0106007b" />
+  <public type="color" name="system_control_activated_light" id="0x0106007c" />
+  <public type="color" name="system_control_normal_light" id="0x0106007d" />
+  <public type="color" name="system_control_highlight_light" id="0x0106007e" />
+  <public type="color" name="system_text_primary_inverse_light" id="0x0106007f" />
+  <public type="color" name="system_text_secondary_and_tertiary_inverse_light" id="0x01060080" />
+  <public type="color" name="system_text_primary_inverse_disable_only_light" id="0x01060081" />
+  <public type="color" name="system_text_secondary_and_tertiary_inverse_disabled_light" id="0x01060082" />
+  <public type="color" name="system_text_hint_inverse_light" id="0x01060083" />
+  <public type="color" name="system_palette_key_color_primary_light" id="0x01060084" />
+  <public type="color" name="system_palette_key_color_secondary_light" id="0x01060085" />
+  <public type="color" name="system_palette_key_color_tertiary_light" id="0x01060086" />
+  <public type="color" name="system_palette_key_color_neutral_light" id="0x01060087" />
+  <public type="color" name="system_palette_key_color_neutral_variant_light" id="0x01060088" />
+  <public type="color" name="system_primary_container_dark" id="0x01060089" />
+  <public type="color" name="system_on_primary_container_dark" id="0x0106008a" />
+  <public type="color" name="system_primary_dark" id="0x0106008b" />
+  <public type="color" name="system_on_primary_dark" id="0x0106008c" />
+  <public type="color" name="system_secondary_container_dark" id="0x0106008d" />
+  <public type="color" name="system_on_secondary_container_dark" id="0x0106008e" />
+  <public type="color" name="system_secondary_dark" id="0x0106008f" />
+  <public type="color" name="system_on_secondary_dark" id="0x01060090" />
+  <public type="color" name="system_tertiary_container_dark" id="0x01060091" />
+  <public type="color" name="system_on_tertiary_container_dark" id="0x01060092" />
+  <public type="color" name="system_tertiary_dark" id="0x01060093" />
+  <public type="color" name="system_on_tertiary_dark" id="0x01060094" />
+  <public type="color" name="system_background_dark" id="0x01060095" />
+  <public type="color" name="system_on_background_dark" id="0x01060096" />
+  <public type="color" name="system_surface_dark" id="0x01060097" />
+  <public type="color" name="system_on_surface_dark" id="0x01060098" />
+  <public type="color" name="system_surface_container_low_dark" id="0x01060099" />
+  <public type="color" name="system_surface_container_lowest_dark" id="0x0106009a" />
+  <public type="color" name="system_surface_container_dark" id="0x0106009b" />
+  <public type="color" name="system_surface_container_high_dark" id="0x0106009c" />
+  <public type="color" name="system_surface_container_highest_dark" id="0x0106009d" />
+  <public type="color" name="system_surface_bright_dark" id="0x0106009e" />
+  <public type="color" name="system_surface_dim_dark" id="0x0106009f" />
+  <public type="color" name="system_surface_variant_dark" id="0x010600a0" />
+  <public type="color" name="system_on_surface_variant_dark" id="0x010600a1" />
+  <public type="color" name="system_outline_dark" id="0x010600a2" />
+  <public type="color" name="system_error_dark" id="0x010600a3" />
+  <public type="color" name="system_on_error_dark" id="0x010600a4" />
+  <public type="color" name="system_error_container_dark" id="0x010600a5" />
+  <public type="color" name="system_on_error_container_dark" id="0x010600a6" />
+  <public type="color" name="system_control_activated_dark" id="0x010600a7" />
+  <public type="color" name="system_control_normal_dark" id="0x010600a8" />
+  <public type="color" name="system_control_highlight_dark" id="0x010600a9" />
+  <public type="color" name="system_text_primary_inverse_dark" id="0x010600aa" />
+  <public type="color" name="system_text_secondary_and_tertiary_inverse_dark" id="0x010600ab" />
+  <public type="color" name="system_text_primary_inverse_disable_only_dark" id="0x010600ac" />
+  <public type="color" name="system_text_secondary_and_tertiary_inverse_disabled_dark" id="0x010600ad" />
+  <public type="color" name="system_text_hint_inverse_dark" id="0x010600ae" />
+  <public type="color" name="system_palette_key_color_primary_dark" id="0x010600af" />
+  <public type="color" name="system_palette_key_color_secondary_dark" id="0x010600b0" />
+  <public type="color" name="system_palette_key_color_tertiary_dark" id="0x010600b1" />
+  <public type="color" name="system_palette_key_color_neutral_dark" id="0x010600b2" />
+  <public type="color" name="system_palette_key_color_neutral_variant_dark" id="0x010600b3" />
+  <public type="color" name="system_primary_fixed" id="0x010600b4" />
+  <public type="color" name="system_primary_fixed_dim" id="0x010600b5" />
+  <public type="color" name="system_on_primary_fixed" id="0x010600b6" />
+  <public type="color" name="system_on_primary_fixed_variant" id="0x010600b7" />
+  <public type="color" name="system_secondary_fixed" id="0x010600b8" />
+  <public type="color" name="system_secondary_fixed_dim" id="0x010600b9" />
+  <public type="color" name="system_on_secondary_fixed" id="0x010600ba" />
+  <public type="color" name="system_on_secondary_fixed_variant" id="0x010600bb" />
+  <public type="color" name="system_tertiary_fixed" id="0x010600bc" />
+  <public type="color" name="system_tertiary_fixed_dim" id="0x010600bd" />
+  <public type="color" name="system_on_tertiary_fixed" id="0x010600be" />
+  <public type="color" name="system_on_tertiary_fixed_variant" id="0x010600bf" />
+  <public type="color" name="system_outline_variant_light" id="0x010600c0" />
+  <public type="color" name="system_outline_variant_dark" id="0x010600c1" />
+
+  <staging-public-group-final type="bool" first-id="0x01be0000">
+    <!-- @hide @SystemApi -->
+    <public name="config_safetyProtectionEnabled" />
+    <!-- @hide @SystemApi -->
+    <public name="config_enableDefaultNotes" />
+    <!-- @hide @SystemApi -->
+    <public name="config_enableDefaultNotesForWorkProfile" />
+  </staging-public-group-final>
+
+    <!-- @hide @SystemApi -->
+  <public type="bool" name="config_safetyProtectionEnabled" id="0x01110009" />
+    <!-- @hide @SystemApi -->
+  <public type="bool" name="config_enableDefaultNotes" id="0x0111000a" />
+    <!-- @hide @SystemApi -->
+  <public type="bool" name="config_enableDefaultNotesForWorkProfile" id="0x0111000b" />
+
 </resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 9cbf3b6..49a1940 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -102,231 +102,65 @@
 <resources>
 
   <!-- ===============================================================
-    Resources added in version U of the platform
+    Resources added in version NEXT of the platform
 
     NOTE: After this version of the platform is forked, changes cannot be made to the root
     branch's groups for that release. Only merge changes to the forked platform branch.
     =============================================================== -->
   <eat-comment/>
 
-  <staging-public-group type="attr" first-id="0x01ce0000">
-    <public name="handwritingBoundsOffsetLeft" />
-    <public name="handwritingBoundsOffsetTop" />
-    <public name="handwritingBoundsOffsetRight" />
-    <public name="handwritingBoundsOffsetBottom" />
-    <public name="accessibilityDataSensitive" />
-    <public name="enableTextStylingShortcuts" />
-    <public name="requiredDisplayCategory"/>
-    <public name="removed_maxConcurrentSessionsCount" />
-    <public name="visualQueryDetectionService" />
-    <public name="physicalKeyboardHintLanguageTag" />
-    <public name="physicalKeyboardHintLayoutType" />
-    <public name="allowSharedIsolatedProcess" />
-    <public name="keyboardLocale" />
-    <public name="keyboardLayoutType" />
-    <public name="allowUpdateOwnership" />
-    <public name="isCredential"/>
-    <public name="searchResultHighlightColor" />
-    <public name="focusedSearchResultHighlightColor" />
-    <public name="stylusHandwritingSettingsActivity" />
-    <public name="windowNoMoveAnimation" />
-    <public name="settingsSubtitle" />
-    <public name="capability" />
+  <staging-public-group type="attr" first-id="0x01bd0000">
   </staging-public-group>
 
-  <staging-public-group type="id" first-id="0x01cd0000">
-    <public name="bold" />
-    <public name="italic" />
-    <public name="underline" />
-    <public name="accessibilityActionScrollInDirection" />
+  <staging-public-group type="id" first-id="0x01bc0000">
   </staging-public-group>
 
-  <staging-public-group type="style" first-id="0x01cc0000">
+  <staging-public-group type="style" first-id="0x01bb0000">
   </staging-public-group>
 
-  <staging-public-group type="string" first-id="0x01cb0000">
-    <!-- @hide @SystemApi -->
-    <public name="config_systemWearHealthService" />
-    <!-- @hide @SystemApi -->
-    <public name="config_defaultNotes" />
-    <!-- @hide @SystemApi -->
-    <public name="config_systemFinancedDeviceController" />
-    <!-- @hide @SystemApi -->
-    <public name="config_systemCallStreaming" />
+  <staging-public-group type="string" first-id="0x01ba0000">
   </staging-public-group>
 
-  <staging-public-group type="dimen" first-id="0x01ca0000">
-    <!-- @hide @SystemApi -->
-    <public name="config_viewConfigurationHandwritingGestureLineMargin" />
+  <staging-public-group type="dimen" first-id="0x01b90000">
   </staging-public-group>
 
-  <staging-public-group type="color" first-id="0x01c90000">
-    <public name="system_primary_container_light" />
-    <public name="system_on_primary_container_light" />
-    <public name="system_primary_light" />
-    <public name="system_on_primary_light" />
-    <public name="system_secondary_container_light" />
-    <public name="system_on_secondary_container_light" />
-    <public name="system_secondary_light" />
-    <public name="system_on_secondary_light" />
-    <public name="system_tertiary_container_light" />
-    <public name="system_on_tertiary_container_light" />
-    <public name="system_tertiary_light" />
-    <public name="system_on_tertiary_light" />
-    <public name="system_background_light" />
-    <public name="system_on_background_light" />
-    <public name="system_surface_light" />
-    <public name="system_on_surface_light" />
-    <public name="system_surface_container_low_light" />
-    <public name="system_surface_container_lowest_light" />
-    <public name="system_surface_container_light" />
-    <public name="system_surface_container_high_light" />
-    <public name="system_surface_container_highest_light" />
-    <public name="system_surface_bright_light" />
-    <public name="system_surface_dim_light" />
-    <public name="system_surface_variant_light" />
-    <public name="system_on_surface_variant_light" />
-    <public name="system_outline_light" />
-    <public name="system_error_light" />
-    <public name="system_on_error_light" />
-    <public name="system_error_container_light" />
-    <public name="system_on_error_container_light" />
-    <public name="removed_system_primary_fixed_light" />
-    <public name="removed_system_primary_fixed_dim_light" />
-    <public name="removed_system_on_primary_fixed_light" />
-    <public name="removed_system_on_primary_fixed_variant_light" />
-    <public name="removed_system_secondary_fixed_light" />
-    <public name="removed_system_secondary_fixed_dim_light" />
-    <public name="removed_system_on_secondary_fixed_light" />
-    <public name="removed_system_on_secondary_fixed_variant_light" />
-    <public name="removed_system_tertiary_fixed_light" />
-    <public name="removed_system_tertiary_fixed_dim_light" />
-    <public name="removed_system_on_tertiary_fixed_light" />
-    <public name="removed_system_on_tertiary_fixed_variant_light" />
-    <public name="system_control_activated_light" />
-    <public name="system_control_normal_light" />
-    <public name="system_control_highlight_light" />
-    <public name="system_text_primary_inverse_light" />
-    <public name="system_text_secondary_and_tertiary_inverse_light" />
-    <public name="system_text_primary_inverse_disable_only_light" />
-    <public name="system_text_secondary_and_tertiary_inverse_disabled_light" />
-    <public name="system_text_hint_inverse_light" />
-    <public name="system_palette_key_color_primary_light" />
-    <public name="system_palette_key_color_secondary_light" />
-    <public name="system_palette_key_color_tertiary_light" />
-    <public name="system_palette_key_color_neutral_light" />
-    <public name="system_palette_key_color_neutral_variant_light" />
-    <public name="system_primary_container_dark"/>
-    <public name="system_on_primary_container_dark"/>
-    <public name="system_primary_dark"/>
-    <public name="system_on_primary_dark"/>
-    <public name="system_secondary_container_dark"/>
-    <public name="system_on_secondary_container_dark"/>
-    <public name="system_secondary_dark"/>
-    <public name="system_on_secondary_dark"/>
-    <public name="system_tertiary_container_dark"/>
-    <public name="system_on_tertiary_container_dark"/>
-    <public name="system_tertiary_dark"/>
-    <public name="system_on_tertiary_dark"/>
-    <public name="system_background_dark"/>
-    <public name="system_on_background_dark"/>
-    <public name="system_surface_dark"/>
-    <public name="system_on_surface_dark"/>
-    <public name="system_surface_container_low_dark"/>
-    <public name="system_surface_container_lowest_dark"/>
-    <public name="system_surface_container_dark"/>
-    <public name="system_surface_container_high_dark"/>
-    <public name="system_surface_container_highest_dark"/>
-    <public name="system_surface_bright_dark"/>
-    <public name="system_surface_dim_dark"/>
-    <public name="system_surface_variant_dark"/>
-    <public name="system_on_surface_variant_dark"/>
-    <public name="system_outline_dark"/>
-    <public name="system_error_dark"/>
-    <public name="system_on_error_dark"/>
-    <public name="system_error_container_dark"/>
-    <public name="system_on_error_container_dark"/>
-    <public name="removed_system_primary_fixed_dark"/>
-    <public name="removed_system_primary_fixed_dim_dark"/>
-    <public name="removed_system_on_primary_fixed_dark"/>
-    <public name="removed_system_on_primary_fixed_variant_dark"/>
-    <public name="removed_system_secondary_fixed_dark"/>
-    <public name="removed_system_secondary_fixed_dim_dark"/>
-    <public name="removed_system_on_secondary_fixed_dark"/>
-    <public name="removed_system_on_secondary_fixed_variant_dark"/>
-    <public name="removed_system_tertiary_fixed_dark"/>
-    <public name="removed_system_tertiary_fixed_dim_dark"/>
-    <public name="removed_system_on_tertiary_fixed_dark"/>
-    <public name="removed_system_on_tertiary_fixed_variant_dark"/>
-    <public name="system_control_activated_dark"/>
-    <public name="system_control_normal_dark"/>
-    <public name="system_control_highlight_dark"/>
-    <public name="system_text_primary_inverse_dark"/>
-    <public name="system_text_secondary_and_tertiary_inverse_dark"/>
-    <public name="system_text_primary_inverse_disable_only_dark"/>
-    <public name="system_text_secondary_and_tertiary_inverse_disabled_dark"/>
-    <public name="system_text_hint_inverse_dark"/>
-    <public name="system_palette_key_color_primary_dark"/>
-    <public name="system_palette_key_color_secondary_dark"/>
-    <public name="system_palette_key_color_tertiary_dark"/>
-    <public name="system_palette_key_color_neutral_dark"/>
-    <public name="system_palette_key_color_neutral_variant_dark"/>
-    <public name="system_primary_fixed" />
-    <public name="system_primary_fixed_dim" />
-    <public name="system_on_primary_fixed" />
-    <public name="system_on_primary_fixed_variant" />
-    <public name="system_secondary_fixed" />
-    <public name="system_secondary_fixed_dim" />
-    <public name="system_on_secondary_fixed" />
-    <public name="system_on_secondary_fixed_variant" />
-    <public name="system_tertiary_fixed" />
-    <public name="system_tertiary_fixed_dim" />
-    <public name="system_on_tertiary_fixed" />
-    <public name="system_on_tertiary_fixed_variant" />
-    <public name="system_outline_variant_light" />
-    <public name="system_outline_variant_dark" />
+  <staging-public-group type="color" first-id="0x01b80000">
   </staging-public-group>
 
-  <staging-public-group type="array" first-id="0x01c80000">
+  <staging-public-group type="array" first-id="0x01b70000">
   </staging-public-group>
 
-  <staging-public-group type="drawable" first-id="0x01c70000">
+  <staging-public-group type="drawable" first-id="0x01b60000">
   </staging-public-group>
 
-  <staging-public-group type="layout" first-id="0x01c60000">
+  <staging-public-group type="layout" first-id="0x01b50000">
   </staging-public-group>
 
-  <staging-public-group type="anim" first-id="0x01c50000">
+  <staging-public-group type="anim" first-id="0x01b40000">
   </staging-public-group>
 
-  <staging-public-group type="animator" first-id="0x01c40000">
+  <staging-public-group type="animator" first-id="0x01b30000">
   </staging-public-group>
 
-  <staging-public-group type="interpolator" first-id="0x01c30000">
+  <staging-public-group type="interpolator" first-id="0x01b20000">
   </staging-public-group>
 
-  <staging-public-group type="mipmap" first-id="0x01c20000">
+  <staging-public-group type="mipmap" first-id="0x01b10000">
   </staging-public-group>
 
-  <staging-public-group type="integer" first-id="0x01c10000">
+  <staging-public-group type="integer" first-id="0x01b00000">
   </staging-public-group>
 
-  <staging-public-group type="transition" first-id="0x01c00000">
+  <staging-public-group type="transition" first-id="0x01af0000">
   </staging-public-group>
 
-  <staging-public-group type="raw" first-id="0x01bf0000">
+  <staging-public-group type="raw" first-id="0x01ae0000">
   </staging-public-group>
 
-  <staging-public-group type="bool" first-id="0x01be0000">
-    <!-- @hide @SystemApi -->
-    <public name="config_safetyProtectionEnabled" />
-    <!-- @hide @SystemApi -->
-    <public name="config_enableDefaultNotes" />
-    <!-- @hide @SystemApi -->
-    <public name="config_enableDefaultNotesForWorkProfile" />
+  <staging-public-group type="bool" first-id="0x01ad0000">
   </staging-public-group>
 
-  <staging-public-group type="fraction" first-id="0x01bd0000">
+  <staging-public-group type="fraction" first-id="0x01ac0000">
   </staging-public-group>
 
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8231407..947dc2d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1347,16 +1347,6 @@
     <!-- Description of the background body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors in the background. [CHAR LIMIT=NONE] -->
     <string name="permdesc_bodySensors_background" product="default">Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in the background.</string>
 
-    <!-- Title of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access body sensor wrist temperature data. [CHAR LIMIT=NONE] -->
-    <string name="permlab_bodySensorsWristTemperature">Access body sensor wrist temperature data while the app is in use.</string>
-    <!-- Description of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] -->
-    <string name="permdesc_bodySensorsWristTemperature" product="default">Allows the app to access body sensor wrist temperature data, while the app is in use.</string>
-
-    <!-- Title of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access body sensor wrist temperature data. [CHAR LIMIT=NONE] -->
-    <string name="permlab_bodySensors_wristTemperature_background">Access body sensor wrist temperature data while the app is in the background.</string>
-    <!-- Description of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] -->
-    <string name="permdesc_bodySensors_wristTemperature_background" product="default">Allows the app to access body sensor wrist temperature data, while the app is in the background.</string>
-
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_readCalendar">Read calendar events and details</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c6c1c8f..a823d1f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2017,6 +2017,7 @@
   <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
   <java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" />
   <java-symbol type="integer" name="config_notificationServiceArchiveSize" />
+  <java-symbol type="array" name="config_useFullScreenIntentPackages" />
   <java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
   <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
   <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index e59b259..7068453 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1665,47 +1665,47 @@
 
         <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
         <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
-        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
         <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
-        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
-        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
-        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
-        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
-        <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+        <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
-        <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
-        <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+        <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+        <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
-        <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
-        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+        <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
-        <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
-        <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+        <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+        <item name="materialColorOnBackground">@color/system_on_background_light</item>
         <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
-        <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
-        <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
-        <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
-        <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
-        <item name="materialColorSecondary">@color/system_secondary_dark</item>
-        <item name="materialColorOnError">@color/system_on_error_dark</item>
-        <item name="materialColorSurface">@color/system_surface_dark</item>
-        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
-        <item name="materialColorTertiary">@color/system_tertiary_dark</item>
-        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
-        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
-        <item name="materialColorOutline">@color/system_outline_dark</item>
-        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
-        <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
-        <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
-        <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+        <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+        <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+        <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+        <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+        <item name="materialColorSecondary">@color/system_secondary_light</item>
+        <item name="materialColorOnError">@color/system_on_error_light</item>
+        <item name="materialColorSurface">@color/system_surface_light</item>
+        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+        <item name="materialColorTertiary">@color/system_tertiary_light</item>
+        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+        <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+        <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+        <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+        <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
     </style>
 
     <!-- DeviceDefault style for input methods, which is used by the
@@ -1758,47 +1758,47 @@
 
         <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
         <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
-        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
         <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
-        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
-        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
-        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
-        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
-        <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+        <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
-        <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
-        <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+        <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+        <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
-        <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
-        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+        <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
-        <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
-        <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+        <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+        <item name="materialColorOnBackground">@color/system_on_background_light</item>
         <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
-        <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
-        <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
-        <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
-        <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
-        <item name="materialColorSecondary">@color/system_secondary_dark</item>
-        <item name="materialColorOnError">@color/system_on_error_dark</item>
-        <item name="materialColorSurface">@color/system_surface_dark</item>
-        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
-        <item name="materialColorTertiary">@color/system_tertiary_dark</item>
-        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
-        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
-        <item name="materialColorOutline">@color/system_outline_dark</item>
-        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
-        <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
-        <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
-        <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+        <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+        <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+        <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+        <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+        <item name="materialColorSecondary">@color/system_secondary_light</item>
+        <item name="materialColorOnError">@color/system_on_error_light</item>
+        <item name="materialColorSurface">@color/system_surface_light</item>
+        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+        <item name="materialColorTertiary">@color/system_tertiary_light</item>
+        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+        <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+        <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+        <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+        <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
@@ -4043,16 +4043,16 @@
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
         <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
         <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
         <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
         <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
         <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
@@ -4123,16 +4123,16 @@
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
         <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
         <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
         <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
         <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
         <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
@@ -4195,16 +4195,16 @@
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
         <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
         <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
         <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
         <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
         <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
@@ -4366,16 +4366,16 @@
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
         <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
         <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
         <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
         <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
         <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
@@ -4475,47 +4475,47 @@
 
         <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
         <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
-        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
         <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
-        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
-        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
-        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
-        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
-        <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+        <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
-        <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
-        <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+        <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+        <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
-        <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
-        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+        <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
-        <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
-        <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+        <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+        <item name="materialColorOnBackground">@color/system_on_background_light</item>
         <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
-        <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
-        <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
-        <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
-        <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
-        <item name="materialColorSecondary">@color/system_secondary_dark</item>
-        <item name="materialColorOnError">@color/system_on_error_dark</item>
-        <item name="materialColorSurface">@color/system_surface_dark</item>
-        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
-        <item name="materialColorTertiary">@color/system_tertiary_dark</item>
-        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
-        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
-        <item name="materialColorOutline">@color/system_outline_dark</item>
-        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
-        <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
-        <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
-        <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+        <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+        <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+        <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+        <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+        <item name="materialColorSecondary">@color/system_secondary_light</item>
+        <item name="materialColorOnError">@color/system_on_error_light</item>
+        <item name="materialColorSurface">@color/system_surface_light</item>
+        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+        <item name="materialColorTertiary">@color/system_tertiary_light</item>
+        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+        <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+        <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+        <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+        <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.Settings.Dialog.Alert" parent="Theme.Material.Settings.Dialog.Alert">
@@ -4570,47 +4570,47 @@
 
         <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
         <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
-        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
         <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
-        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
-        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
-        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
-        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
-        <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+        <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
-        <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
-        <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+        <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+        <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
-        <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
-        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+        <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
-        <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
-        <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+        <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+        <item name="materialColorOnBackground">@color/system_on_background_light</item>
         <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
-        <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
-        <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
-        <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
-        <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
-        <item name="materialColorSecondary">@color/system_secondary_dark</item>
-        <item name="materialColorOnError">@color/system_on_error_dark</item>
-        <item name="materialColorSurface">@color/system_surface_dark</item>
-        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
-        <item name="materialColorTertiary">@color/system_tertiary_dark</item>
-        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
-        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
-        <item name="materialColorOutline">@color/system_outline_dark</item>
-        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
-        <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
-        <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
-        <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+        <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+        <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+        <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+        <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+        <item name="materialColorSecondary">@color/system_secondary_light</item>
+        <item name="materialColorOnError">@color/system_on_error_light</item>
+        <item name="materialColorSurface">@color/system_surface_light</item>
+        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+        <item name="materialColorTertiary">@color/system_tertiary_light</item>
+        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+        <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+        <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+        <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+        <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar" />
@@ -4700,16 +4700,16 @@
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
         <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
         <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
         <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
         <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
         <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
diff --git a/core/tests/coretests/src/android/app/UiAutomationTest.java b/core/tests/coretests/src/android/app/UiAutomationTest.java
new file mode 100644
index 0000000..3ac5057
--- /dev/null
+++ b/core/tests/coretests/src/android/app/UiAutomationTest.java
@@ -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 android.app;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Looper;
+import android.os.UserManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class UiAutomationTest {
+
+    private static final int SECONDARY_DISPLAY_ID = 42;
+    private static final int DISPLAY_ID_ASSIGNED_TO_USER = 108;
+
+    private final Looper mLooper = Looper.getMainLooper();
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private IUiAutomationConnection mConnection;
+
+    @Before
+    public void setFixtures() {
+        when(mContext.getMainLooper()).thenReturn(mLooper);
+        mockSystemService(UserManager.class, mUserManager);
+
+        // Set default expectations
+        mockVisibleBackgroundUsersSupported(/* supported= */ false);
+        mockUserVisibility(/* visible= */ true);
+        // make sure it's not used, unless explicitly mocked
+        mockDisplayAssignedToUser(INVALID_DISPLAY);
+        mockContextDisplay(DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testContextConstructor_nullContext() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new UiAutomation((Context) null, mConnection));
+    }
+
+    @Test
+    public void testContextConstructor_nullConnection() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new UiAutomation(mContext, (IUiAutomationConnection) null));
+    }
+
+    @Test
+    public void testGetDisplay_contextWithSecondaryDisplayId() {
+        mockContextDisplay(SECONDARY_DISPLAY_ID);
+
+        UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+        // It's always DEFAULT_DISPLAY regardless, unless the device supports visible bg users
+        assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+                .isEqualTo(DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testGetDisplay_contextWithInvalidDisplayId() {
+        mockContextDisplay(INVALID_DISPLAY);
+
+        UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+        assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+                .isEqualTo(DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testGetDisplay_visibleBgUsers() {
+        mockVisibleBackgroundUsersSupported(/* supported= */ true);
+        mockContextDisplay(SECONDARY_DISPLAY_ID);
+        // Should be using display from context, not from user
+        mockDisplayAssignedToUser(DISPLAY_ID_ASSIGNED_TO_USER);
+
+        UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+        assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+                .isEqualTo(SECONDARY_DISPLAY_ID);
+    }
+
+    @Test
+    public void testGetDisplay_visibleBgUsers_contextWithInvalidDisplayId() {
+        mockVisibleBackgroundUsersSupported(/* supported= */ true);
+        mockContextDisplay(INVALID_DISPLAY);
+        mockDisplayAssignedToUser(DISPLAY_ID_ASSIGNED_TO_USER);
+
+        UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+        assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+                .isEqualTo(DISPLAY_ID_ASSIGNED_TO_USER);
+    }
+
+    private <T> void mockSystemService(Class<T> svcClass, T svc) {
+        String svcName = svcClass.getName();
+        when(mContext.getSystemServiceName(svcClass)).thenReturn(svcName);
+        when(mContext.getSystemService(svcName)).thenReturn(svc);
+    }
+
+    private void mockVisibleBackgroundUsersSupported(boolean supported) {
+        when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(supported);
+    }
+
+    private void mockContextDisplay(int displayId) {
+        when(mContext.getDisplayId()).thenReturn(displayId);
+    }
+
+    private void mockDisplayAssignedToUser(int displayId) {
+        when(mUserManager.getMainDisplayIdAssignedToUser()).thenReturn(displayId);
+    }
+
+    private void mockUserVisibility(boolean visible) {
+        when(mUserManager.isUserVisible()).thenReturn(visible);
+    }
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 980211f..c6bb07b 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -25,6 +25,7 @@
 import android.app.Activity;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.os.Bundle;
+import android.platform.test.annotations.IwTest;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.util.PollingCheck;
@@ -84,6 +85,7 @@
         }
     }
 
+    @IwTest(focusArea = "accessibility")
     @Test
     public void testFontsScaleNonLinearly() {
         final ActivityScenario<TestActivity> scenario = rule.getScenario();
@@ -114,6 +116,7 @@
         )));
     }
 
+    @IwTest(focusArea = "accessibility")
     @Test
     public void testOnConfigurationChanged_doesNotCrash() {
         final ActivityScenario<TestActivity> scenario = rule.getScenario();
@@ -127,6 +130,7 @@
         });
     }
 
+    @IwTest(focusArea = "accessibility")
     @Test
     public void testUpdateConfiguration_doesNotCrash() {
         final ActivityScenario<TestActivity> scenario = rule.getScenario();
diff --git a/core/tests/coretests/src/android/content/res/TEST_MAPPING b/core/tests/coretests/src/android/content/res/TEST_MAPPING
index 4ea6e40..ab14950 100644
--- a/core/tests/coretests/src/android/content/res/TEST_MAPPING
+++ b/core/tests/coretests/src/android/content/res/TEST_MAPPING
@@ -39,5 +39,18 @@
         }
       ]
     }
+  ],
+  "ironwood-postsubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options":[
+        {
+            "include-annotation": "android.platform.test.annotations.IwTest"
+        },
+        {
+            "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
   ]
 }
diff --git a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
new file mode 100644
index 0000000..66f3bca
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.hardware.biometrics;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.Executor;
+
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class BiometricPromptTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private IAuthService mService;
+    private BiometricPrompt mBiometricPrompt;
+
+    private CancellationSignal mCancellationSignal;
+
+    private final TestLooper mLooper = new TestLooper();
+    private final Handler mHandler = new Handler(mLooper.getLooper());
+    private final Executor mExecutor = mHandler::post;
+
+    @Before
+    public void setUp() throws RemoteException {
+        mBiometricPrompt = new BiometricPrompt.Builder(mContext)
+                .setUseDefaultSubtitle()
+                .setUseDefaultTitle()
+                .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG
+                        | BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+                .setService(mService)
+                .build();
+
+        mCancellationSignal = new CancellationSignal();
+        when(mService.authenticate(any(), anyLong(), anyInt(), any(), anyString(), any()))
+                .thenReturn(0L);
+        when(mContext.getPackageName()).thenReturn("BiometricPromptTest");
+    }
+
+    @Test
+    public void testCancellationAfterAuthenticationFailed() throws RemoteException {
+        ArgumentCaptor<IBiometricServiceReceiver> biometricServiceReceiverCaptor =
+                ArgumentCaptor.forClass(IBiometricServiceReceiver.class);
+        BiometricPrompt.AuthenticationCallback callback =
+                new BiometricPrompt.AuthenticationCallback() {
+            @Override
+            public void onAuthenticationError(int errorCode, CharSequence errString) {
+                super.onAuthenticationError(errorCode, errString);
+            }};
+        mBiometricPrompt.authenticate(mCancellationSignal, mExecutor, callback);
+        mLooper.dispatchAll();
+
+        verify(mService).authenticate(any(), anyLong(), anyInt(),
+                biometricServiceReceiverCaptor.capture(), anyString(), any());
+
+        biometricServiceReceiverCaptor.getValue().onAuthenticationFailed();
+        mLooper.dispatchAll();
+        mCancellationSignal.cancel();
+
+        verify(mService).cancelAuthentication(any(), anyString(), anyLong());
+    }
+}
diff --git a/core/tests/coretests/src/android/hardware/biometrics/OWNERS b/core/tests/coretests/src/android/hardware/biometrics/OWNERS
new file mode 100644
index 0000000..6a2192a
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/biometrics/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 3b6e8ea..cde100c 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -70,7 +70,13 @@
     private ApplicationInfo mApplicationInfo;
 
     private final BackMotionEvent mBackEvent = new BackMotionEvent(
-            0, 0, 0, BackEvent.EDGE_LEFT, null);
+            /* touchX = */ 0,
+            /* touchY = */ 0,
+            /* progress = */ 0,
+            /* velocityX = */ 0,
+            /* velocityY = */ 0,
+            /* swipeEdge = */ BackEvent.EDGE_LEFT,
+            /* departingAnimationTarget = */ null);
 
     @Before
     public void setUp() throws Exception {
diff --git a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
index 4716312..36c2a62 100644
--- a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
@@ -23,16 +23,25 @@
 import static com.android.internal.util.DumpUtils.isPlatformNonCriticalPackage;
 import static com.android.internal.util.DumpUtils.isPlatformPackage;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.content.ComponentName;
+import android.util.SparseArray;
 
 import junit.framework.TestCase;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
 /**
  * Run with:
  atest FrameworksCoreTests:DumpUtilsTest
  */
 public class DumpUtilsTest extends TestCase {
 
+    private final StringWriter mStringWriter = new StringWriter();
+    private final PrintWriter mPrintWriter = new PrintWriter(mStringWriter);
+
     private static ComponentName cn(String componentName) {
         if (componentName == null) {
             return null;
@@ -168,4 +177,144 @@
                 Integer.toHexString(System.identityHashCode(component))).test(
                         wcn("com.google/.abc")));
     }
+
+    public void testDumpSparseArray_empty() {
+        SparseArray<String> array = new SparseArray<>();
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ "...", array, "whatever");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("empty array dump").that(output).isEqualTo("...No whatevers\n");
+    }
+
+    public void testDumpSparseArray_oneElement() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".1 number(s):\n"
+                + "..0: 1->uno\n");
+    }
+
+    public void testDumpSparseArray_oneNullElement() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "NULL");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".1 NULL(s):\n"
+                + "..0: 1->(null)\n");
+    }
+
+    public void testDumpSparseArray_multipleElements() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 number(s):\n"
+                + "..0: 1->uno\n"
+                + "..1: 2->duo\n"
+                + "..2: 42->(null)\n");
+    }
+
+    public void testDumpSparseArray_keyDumperOnly() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+                (i, k) -> {
+                    mPrintWriter.printf("_%d=%d_", i, k);
+                }, /* valueDumper= */ null);
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 number(s):\n"
+                + "_0=1_uno\n"
+                + "_1=2_duo\n"
+                + "_2=42_(null)\n");
+    }
+
+    public void testDumpSparseArray_valueDumperOnly() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+                /* keyDumper= */ null,
+                s -> {
+                    mPrintWriter.print(s.toUpperCase());
+                });
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 number(s):\n"
+                + "..0: 1->UNO\n"
+                + "..1: 2->DUO\n"
+                + "..2: 42->(null)\n");
+    }
+
+    public void testDumpSparseArray_keyAndValueDumpers() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+                (i, k) -> {
+                    mPrintWriter.printf("_%d=%d_", i, k);
+                },
+                s -> {
+                    mPrintWriter.print(s.toUpperCase());
+                });
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 number(s):\n"
+                + "_0=1_UNO\n"
+                + "_1=2_DUO\n"
+                + "_2=42_(null)\n");
+    }
+
+    public void testDumpSparseArrayValues() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArrayValues(mPrintWriter, /* prefix= */ ".", array, "number");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 numbers:\n"
+                + "..uno\n"
+                + "..duo\n"
+                + "..(null)\n");
+    }
+
+    private String flushPrintWriter() {
+        mPrintWriter.flush();
+
+        return mStringWriter.toString();
+    }
 }
diff --git a/core/tests/expresslog/AndroidManifest.xml b/core/tests/expresslog/AndroidManifest.xml
deleted file mode 100644
index 94a39e0..0000000
--- a/core/tests/expresslog/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          android:installLocation="internalOnly"
-          package="com.android.internal.expresslog" >
-
-    <application >
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.internal.expresslog"
-            android:label="Telemetry Express Logging Helper Tests" />
-
-</manifest>
diff --git a/core/tests/expresslog/OWNERS b/core/tests/expresslog/OWNERS
deleted file mode 100644
index 3dc958b..0000000
--- a/core/tests/expresslog/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 719316
-# Stats/expresslog
-file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/tests/expresslog/TEST_MAPPING b/core/tests/expresslog/TEST_MAPPING
deleted file mode 100644
index c9b0cf8..0000000
--- a/core/tests/expresslog/TEST_MAPPING
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "ExpressLogTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
deleted file mode 100644
index ee62d75..0000000
--- a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
+++ /dev/null
@@ -1,167 +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.internal.expresslog;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-@SmallTest
-public class ScaledRangeOptionsTest {
-    private static final String TAG = ScaledRangeOptionsTest.class.getSimpleName();
-
-    @Test
-    public void testGetBinsCount() {
-        Histogram.ScaledRangeOptions options1 = new Histogram.ScaledRangeOptions(1, 100, 100, 2);
-        assertEquals(3, options1.getBinsCount());
-
-        Histogram.ScaledRangeOptions options10 = new Histogram.ScaledRangeOptions(10, 100, 100, 2);
-        assertEquals(12, options10.getBinsCount());
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructZeroBinsCount() {
-        new Histogram.ScaledRangeOptions(0, 100, 100, 2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructNegativeBinsCount() {
-        new Histogram.ScaledRangeOptions(-1, 100, 100, 2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructNegativeFirstBinWidth() {
-        new Histogram.ScaledRangeOptions(10, 100, -100, 2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructTooSmallFirstBinWidth() {
-        new Histogram.ScaledRangeOptions(10, 100, 0.5f, 2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructNegativeScaleFactor() {
-        new Histogram.ScaledRangeOptions(10, 100, 100, -2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructTooSmallScaleFactor() {
-        new Histogram.ScaledRangeOptions(10, 100, 100, 0.5f);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructTooBigScaleFactor() {
-        new Histogram.ScaledRangeOptions(10, 100, 100, 500.f);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructTooBigBinRange() {
-        new Histogram.ScaledRangeOptions(100, 100, 100, 10.f);
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual1() {
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 1, 1);
-        assertEquals(12, options.getBinsCount());
-
-        assertEquals(11, options.getBinForSample(11));
-
-        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual2() {
-        // this should produce bin otpions similar to linear histogram with bin width 2
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 2, 1);
-        assertEquals(12, options.getBinsCount());
-
-        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i * 2));
-            assertEquals(i, options.getBinForSample(i * 2 - 1));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual5() {
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(2, 0, 5, 1);
-        assertEquals(4, options.getBinsCount());
-        for (int i = 0; i < 2; i++) {
-            for (int sample = 0; sample < 5; sample++) {
-                assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
-            }
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual10() {
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 10, 1);
-        assertEquals(0, options.getBinForSample(0));
-        assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
-        assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
-
-        final float binSize = (101 - 1) / 10f;
-        for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i * binSize));
-        }
-    }
-
-    @Test
-    public void testBinIndexForScaleFactor2() {
-        final int binsCount = 10;
-        final int minValue = 10;
-        final int firstBinWidth = 5;
-        final int scaledFactor = 2;
-
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(
-                binsCount, minValue, firstBinWidth, scaledFactor);
-        assertEquals(binsCount + 2, options.getBinsCount());
-        long[] binCounts = new long[10];
-
-        // precalculate max valid value - start value for the overflow bin
-        int lastBinStartValue = minValue; //firstBinMin value
-        int lastBinWidth = firstBinWidth;
-        for (int binIdx = 2; binIdx <= binsCount + 1; binIdx++) {
-            lastBinStartValue = lastBinStartValue + lastBinWidth;
-            lastBinWidth *= scaledFactor;
-        }
-
-        // underflow bin
-        for (int i = 1; i < minValue; i++) {
-            assertEquals(0, options.getBinForSample(i));
-        }
-
-        for (int i = 10; i < lastBinStartValue; i++) {
-            assertTrue(options.getBinForSample(i) > 0);
-            assertTrue(options.getBinForSample(i) <= binsCount);
-            binCounts[options.getBinForSample(i) - 1]++;
-        }
-
-        // overflow bin
-        assertEquals(binsCount + 1, options.getBinForSample(lastBinStartValue));
-
-        for (int i = 1; i < binsCount; i++) {
-            assertEquals(binCounts[i], binCounts[i - 1] * 2L);
-        }
-    }
-}
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
deleted file mode 100644
index 037dbb3..0000000
--- a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
+++ /dev/null
@@ -1,125 +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.internal.expresslog;
-
-import androidx.test.filters.SmallTest;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-@SmallTest
-public class UniformOptionsTest {
-    private static final String TAG = UniformOptionsTest.class.getSimpleName();
-
-    @Test
-    public void testGetBinsCount() {
-        Histogram.UniformOptions options1 = new Histogram.UniformOptions(1, 100, 1000);
-        assertEquals(3, options1.getBinsCount());
-
-        Histogram.UniformOptions options10 = new Histogram.UniformOptions(10, 100, 1000);
-        assertEquals(12, options10.getBinsCount());
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructZeroBinsCount() {
-        new Histogram.UniformOptions(0, 100, 1000);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructNegativeBinsCount() {
-        new Histogram.UniformOptions(-1, 100, 1000);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructMaxValueLessThanMinValue() {
-        new Histogram.UniformOptions(10, 1000, 100);
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual1() {
-        Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 11);
-        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual2() {
-        Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 21);
-        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i * 2));
-            assertEquals(i, options.getBinForSample(i * 2 - 1));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual5() {
-        Histogram.UniformOptions options = new Histogram.UniformOptions(2, 0, 10);
-        assertEquals(4, options.getBinsCount());
-        for (int i = 0; i < 2; i++) {
-            for (int sample = 0; sample < 5; sample++) {
-                assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
-            }
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual10() {
-        Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 101);
-        assertEquals(0, options.getBinForSample(0));
-        assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
-        assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
-
-        final float binSize = (101 - 1) / 10f;
-        for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i * binSize));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual90() {
-        final int binCount = 10;
-        final int minValue = 100;
-        final int maxValue = 100000;
-
-        Histogram.UniformOptions options = new Histogram.UniformOptions(binCount, minValue,
-                maxValue);
-
-        // logging underflow sample
-        assertEquals(0, options.getBinForSample(minValue - 1));
-
-        // logging overflow sample
-        assertEquals(binCount + 1, options.getBinForSample(maxValue));
-        assertEquals(binCount + 1, options.getBinForSample(maxValue + 1));
-
-        // logging min edge sample
-        assertEquals(1, options.getBinForSample(minValue));
-
-        // logging max edge sample
-        assertEquals(binCount, options.getBinForSample(maxValue - 1));
-
-        // logging single valid sample per bin
-        final int binSize = (maxValue - minValue) / binCount;
-
-        for (int i = 0; i < binCount; i++) {
-            assertEquals(i + 1, options.getBinForSample(minValue + binSize * i));
-        }
-    }
-}
diff --git a/data/etc/preinstalled-packages-platform-overlays.xml b/data/etc/preinstalled-packages-platform-overlays.xml
index 9959433..2fd65dc 100644
--- a/data/etc/preinstalled-packages-platform-overlays.xml
+++ b/data/etc/preinstalled-packages-platform-overlays.xml
@@ -56,6 +56,9 @@
     <install-in-user-type package="com.android.internal.systemui.navbar.transparent">
         <install-in user-type="FULL" />
     </install-in-user-type>
+    <install-in-user-type package="com.android.role.notes.enabled">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
     <install-in-user-type package="com.android.theme.color.amethyst">
         <install-in user-type="FULL" />
         <install-in user-type="PROFILE" />
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 25b074d..2307d60 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -401,8 +401,9 @@
     /**
      * This is called by methods that want to throw an exception if the bitmap
      * has already been recycled.
+     * @hide
      */
-    private void checkRecycled(String errorMessage) {
+    void checkRecycled(String errorMessage) {
         if (mRecycled) {
             throw new IllegalStateException(errorMessage);
         }
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 2f6dd46..5c06577 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -120,6 +120,7 @@
         if (bitmap == null) {
             throw new IllegalArgumentException("Bitmap must be non-null");
         }
+        bitmap.checkRecycled("Cannot create BitmapShader for recycled bitmap");
         mBitmap = bitmap;
         mTileX = tileX;
         mTileY = tileY;
@@ -188,6 +189,8 @@
     /** @hide */
     @Override
     protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) {
+        mBitmap.checkRecycled("BitmapShader's bitmap has been recycled");
+
         boolean enableLinearFilter = mFilterMode == FILTER_MODE_LINEAR;
         if (mFilterMode == FILTER_MODE_DEFAULT) {
             mFilterFromPaint = filterFromPaint;
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 0b29973..56c3068 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -2083,32 +2083,29 @@
             }
 
             sIsP010SupportedForAV1Initialized = true;
-
-            if (hasHardwareDecoder("video/av01")) {
-                sIsP010SupportedForAV1 = true;
-                return true;
-            }
-
-            sIsP010SupportedForAV1 = Build.VERSION.DEVICE_INITIAL_SDK_INT
-                    >= Build.VERSION_CODES.S;
-            return sIsP010SupportedForAV1;
+            return sIsP010SupportedForAV1 = isP010SupportedforMime("video/av01");
         }
     }
 
     /**
-     * Checks if the device has hardware decoder for the target mime type.
+     * Checks if the device supports decoding 10-bit for the given mime type.
      */
-    private static boolean hasHardwareDecoder(String mime) {
-        final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        for (MediaCodecInfo info : sMCL.getCodecInfos()) {
-            if (info.isEncoder() == false && info.isHardwareAccelerated()) {
-                try {
-                     if (info.getCapabilitiesForType(mime) != null) {
-                         return true;
-                     }
-                } catch (IllegalArgumentException e) {
-                     // mime is not supported
-                     return false;
+    private static boolean isP010SupportedforMime(String mime) {
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo mediaCodecInfo : codecList.getCodecInfos()) {
+            if (mediaCodecInfo.isEncoder()) {
+                continue;
+            }
+            for (String mediaType : mediaCodecInfo.getSupportedTypes()) {
+                if (mediaType.equalsIgnoreCase(mime)) {
+                    MediaCodecInfo.CodecCapabilities codecCapabilities =
+                        mediaCodecInfo.getCapabilitiesForType(mediaType);
+                    for (int i = 0; i < codecCapabilities.colorFormats.length; ++i) {
+                        if (codecCapabilities.colorFormats[i]
+                            == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010) {
+                            return true;
+                        }
+                    }
                 }
             }
         }
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp
index a5b192c..abe8f85 100644
--- a/libs/WindowManager/Jetpack/Android.bp
+++ b/libs/WindowManager/Jetpack/Android.bp
@@ -55,20 +55,6 @@
 }
 
 // Extensions
-// NOTE: This module is still under active development and must not
-// be used in production. Use 'androidx.window.sidecar' instead.
-android_library_import {
-    name: "window-extensions",
-    aars: ["window-extensions-release.aar"],
-    sdk_version: "current",
-}
-
-android_library_import {
-    name: "window-extensions-core",
-    aars: ["window-extensions-core-release.aar"],
-    sdk_version: "current",
-}
-
 java_library {
     name: "androidx.window.extensions",
     srcs: [
@@ -77,8 +63,8 @@
         "src/androidx/window/common/**/*.java",
     ],
     static_libs: [
-        "window-extensions",
-        "window-extensions-core",
+        "androidx.window.extensions_extensions-nodeps",
+        "androidx.window.extensions.core_core-nodeps",
     ],
     installable: true,
     sdk_version: "core_platform",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 8386131..a7a6b3c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -122,16 +122,6 @@
         addWindowLayoutInfoListener(activity, extConsumer);
     }
 
-    @Override
-    public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
-            @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) {
-        final Consumer<WindowLayoutInfo> extConsumer = consumer::accept;
-        synchronized (mLock) {
-            mJavaToExtConsumers.put(consumer, extConsumer);
-        }
-        addWindowLayoutInfoListener(context, extConsumer);
-    }
-
     /**
      * Similar to {@link #addWindowLayoutInfoListener(Activity, java.util.function.Consumer)}, but
      * takes a UI Context as a parameter.
diff --git a/libs/WindowManager/Jetpack/window-extensions-core-release.aar b/libs/WindowManager/Jetpack/window-extensions-core-release.aar
deleted file mode 100644
index 96ff840..0000000
--- a/libs/WindowManager/Jetpack/window-extensions-core-release.aar
+++ /dev/null
Binary files differ
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
deleted file mode 100644
index c3b6916..0000000
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ /dev/null
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index e84a78f..133fd87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -33,12 +33,19 @@
      *
      * @param touchX the X touch position of the {@link MotionEvent}.
      * @param touchY the Y touch position of the {@link MotionEvent}.
+     * @param velocityX the X velocity computed from the {@link MotionEvent}.
+     * @param velocityY the Y velocity computed from the {@link MotionEvent}.
      * @param keyAction the original {@link KeyEvent#getAction()} when the event was dispatched to
      *               the process. This is forwarded separately because the input pipeline may mutate
      *               the {#event} action state later.
      * @param swipeEdge the edge from which the swipe begins.
      */
-    void onBackMotion(float touchX, float touchY, int keyAction,
+    void onBackMotion(
+            float touchX,
+            float touchY,
+            float velocityX,
+            float velocityY,
+            int keyAction,
             @BackEvent.SwipeEdge int swipeEdge);
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 210c9aa..47d3a5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -256,8 +256,20 @@
     private class BackAnimationImpl implements BackAnimation {
         @Override
         public void onBackMotion(
-                float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) {
-            mShellExecutor.execute(() -> onMotionEvent(touchX, touchY, keyAction, swipeEdge));
+                float touchX,
+                float touchY,
+                float velocityX,
+                float velocityY,
+                int keyAction,
+                @BackEvent.SwipeEdge int swipeEdge
+        ) {
+            mShellExecutor.execute(() -> onMotionEvent(
+                    /* touchX = */ touchX,
+                    /* touchY = */ touchY,
+                    /* velocityX = */ velocityX,
+                    /* velocityY = */ velocityY,
+                    /* keyAction = */ keyAction,
+                    /* swipeEdge = */ swipeEdge));
         }
 
         @Override
@@ -332,13 +344,18 @@
      * Called when a new motion event needs to be transferred to this
      * {@link BackAnimationController}
      */
-    public void onMotionEvent(float touchX, float touchY, int keyAction,
+    public void onMotionEvent(
+            float touchX,
+            float touchY,
+            float velocityX,
+            float velocityY,
+            int keyAction,
             @BackEvent.SwipeEdge int swipeEdge) {
         if (mPostCommitAnimationInProgress) {
             return;
         }
 
-        mTouchTracker.update(touchX, touchY);
+        mTouchTracker.update(touchX, touchY, velocityX, velocityY);
         if (keyAction == MotionEvent.ACTION_DOWN) {
             if (!mBackGestureStarted) {
                 mShouldStartOnNextMoveEvent = true;
@@ -561,6 +578,9 @@
         }
         if (runner.isWaitingAnimation()) {
             ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
+            // Supposed it is in post commit animation state, and start the timeout to watch
+            // if the animation is ready.
+            mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
             return;
         } else if (runner.isAnimationCancelled()) {
             invokeOrCancelBack();
@@ -577,6 +597,8 @@
         if (mPostCommitAnimationInProgress) {
             return;
         }
+
+        mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()");
         mPostCommitAnimationInProgress = true;
         mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
@@ -595,9 +617,6 @@
      */
     @VisibleForTesting
     void onBackAnimationFinished() {
-        if (!mPostCommitAnimationInProgress) {
-            return;
-        }
         // Stop timeout runner.
         mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
         mPostCommitAnimationInProgress = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index 695ef4e..904574b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -42,11 +42,13 @@
      */
     private float mInitTouchX;
     private float mInitTouchY;
+    private float mLatestVelocityX;
+    private float mLatestVelocityY;
     private float mStartThresholdX;
     private int mSwipeEdge;
     private boolean mCancelled;
 
-    void update(float touchX, float touchY) {
+    void update(float touchX, float touchY, float velocityX, float velocityY) {
         /**
          * If back was previously cancelled but the user has started swiping in the forward
          * direction again, restart back.
@@ -58,6 +60,8 @@
         }
         mLatestTouchX = touchX;
         mLatestTouchY = touchY;
+        mLatestVelocityX = velocityX;
+        mLatestVelocityY = velocityY;
     }
 
     void setTriggerBack(boolean triggerBack) {
@@ -84,7 +88,14 @@
     }
 
     BackMotionEvent createStartEvent(RemoteAnimationTarget target) {
-        return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
+        return new BackMotionEvent(
+                /* touchX = */ mInitTouchX,
+                /* touchY = */ mInitTouchY,
+                /* progress = */ 0,
+                /* velocityX = */ 0,
+                /* velocityY = */ 0,
+                /* swipeEdge = */ mSwipeEdge,
+                /* departingAnimationTarget = */ target);
     }
 
     BackMotionEvent createProgressEvent() {
@@ -111,7 +122,14 @@
     }
 
     BackMotionEvent createProgressEvent(float progress) {
-        return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+        return new BackMotionEvent(
+                /* touchX = */ mLatestTouchX,
+                /* touchY = */ mLatestTouchY,
+                /* progress = */ progress,
+                /* velocityX = */ mLatestVelocityX,
+                /* velocityY = */ mLatestVelocityY,
+                /* swipeEdge = */ mSwipeEdge,
+                /* departingAnimationTarget = */ null);
     }
 
     public void setProgressThreshold(float progressThreshold) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 670b24c..0400963 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -25,6 +25,7 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.app.WindowConfiguration.WindowingMode
 import android.content.Context
+import android.graphics.Point
 import android.graphics.Rect
 import android.os.IBinder
 import android.os.SystemProperties
@@ -193,6 +194,21 @@
         }
     }
 
+
+    /**
+     * Move a task to fullscreen after being dragged from fullscreen and released back into
+     * status bar area
+     */
+    fun cancelMoveToFreeform(task: RunningTaskInfo, startPosition: Point) {
+        val wct = WindowContainerTransaction()
+        addMoveToFullscreenChanges(wct, task.token)
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition)
+        } else {
+            shellTaskOrganizer.applyTransaction(wct)
+        }
+    }
+
     fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) {
         val wct = WindowContainerTransaction()
         addMoveToFullscreenChanges(wct, task.token)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 3df2340..27eda16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -17,11 +17,13 @@
 package com.android.wm.shell.desktopmode;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.view.SurfaceControl;
@@ -55,6 +57,7 @@
     public static final int FREEFORM_ANIMATION_DURATION = 336;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
+    private Point mStartPosition;
 
     public EnterDesktopTaskTransitionHandler(
             Transitions transitions) {
@@ -79,6 +82,17 @@
         mPendingTransitionTokens.add(token);
     }
 
+    /**
+     * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+     * @param wct WindowContainerTransaction for transition
+     * @param startPosition Position of task when transition is triggered
+     */
+    public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
+            Point startPosition) {
+        mStartPosition = startPosition;
+        startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct);
+    }
+
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startT,
@@ -173,6 +187,37 @@
             return true;
         }
 
+        if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+                && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                && mStartPosition != null) {
+            // This Transition animates a task to fullscreen after being dragged from the status
+            // bar and then released back into the status bar area
+            final SurfaceControl sc = change.getLeash();
+            startT.setWindowCrop(sc, null);
+            startT.apply();
+
+            final ValueAnimator animator = new ValueAnimator();
+            animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f);
+            animator.setDuration(FREEFORM_ANIMATION_DURATION);
+            final SurfaceControl.Transaction t = mTransactionSupplier.get();
+            animator.addUpdateListener(animation -> {
+                final float scale = animation.getAnimatedFraction();
+                t.setPosition(sc, mStartPosition.x * (1 - scale),
+                        mStartPosition.y * (1 - scale));
+                t.setScale(sc, scale, scale);
+                t.apply();
+            });
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mTransitions.getMainExecutor().execute(
+                            () -> finishCallback.onTransitionFinished(null, null));
+                }
+            });
+            animator.start();
+            return true;
+        }
+
         return false;
     }
 
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 f70df83..8c98c77 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
@@ -30,14 +30,17 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
+import android.os.SystemClock;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.transition.Transitions;
 
 import java.lang.annotation.Retention;
@@ -61,6 +64,14 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface AnimationType {}
 
+    /**
+     * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
+     * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
+     * navigation, then the alpha type is unexpected. So use a timeout to avoid applying wrong
+     * animation style to an unrelated task.
+     */
+    private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 800;
+
     public static final int TRANSITION_DIRECTION_NONE = 0;
     public static final int TRANSITION_DIRECTION_SAME = 1;
     public static final int TRANSITION_DIRECTION_TO_PIP = 2;
@@ -109,6 +120,9 @@
             });
 
     private PipTransitionAnimator mCurrentAnimator;
+    @AnimationType
+    private int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+    private long mLastOneShotAlphaAnimationTime;
 
     public PipAnimationController(PipSurfaceTransactionHelper helper) {
         mSurfaceTransactionHelper = helper;
@@ -222,6 +236,37 @@
     }
 
     /**
+     * Sets the preferred enter animation type for one time. This is typically used to set the
+     * animation type to {@link PipAnimationController#ANIM_TYPE_ALPHA}.
+     * <p>
+     * For example, gesture navigation would first fade out the PiP activity, and the transition
+     * should be responsible to animate in (such as fade in) the PiP.
+     */
+    public void setOneShotEnterAnimationType(@AnimationType int animationType) {
+        mOneShotAnimationType = animationType;
+        if (animationType == ANIM_TYPE_ALPHA) {
+            mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
+        }
+    }
+
+    /** Returns the preferred animation type and consumes the one-shot type if needed. */
+    @AnimationType
+    public int takeOneShotEnterAnimationType() {
+        final int type = mOneShotAnimationType;
+        if (type == ANIM_TYPE_ALPHA) {
+            // Restore to default type.
+            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+            if (SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
+                    > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
+                ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "Alpha animation is expired. Use bounds animation.");
+                return ANIM_TYPE_BOUNDS;
+            }
+        }
+        return type;
+    }
+
+    /**
      * Additional callback interface for PiP animation
      */
     public static class PipAnimationCallback {
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 a0bd064..5670fe6 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
@@ -62,7 +62,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.Log;
 import android.view.Choreographer;
@@ -111,12 +110,6 @@
         DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener {
     private static final String TAG = PipTaskOrganizer.class.getSimpleName();
     private static final boolean DEBUG = false;
-    /**
-     * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
-     * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
-     * navigation, then the alpha type is unexpected.
-     */
-    private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000;
 
     /**
      * The fixed start delay in ms when fading out the content overlay from bounds animation.
@@ -301,8 +294,6 @@
     private WindowContainerToken mToken;
     private SurfaceControl mLeash;
     protected PipTransitionState mPipTransitionState;
-    private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
-    private long mLastOneShotAlphaAnimationTime;
     private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
     protected PictureInPictureParams mPictureInPictureParams;
@@ -422,18 +413,6 @@
     }
 
     /**
-     * Sets the preferred animation type for one time.
-     * This is typically used to set the animation type to
-     * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
-     */
-    public void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
-        mOneShotAnimationType = animationType;
-        if (animationType == ANIM_TYPE_ALPHA) {
-            mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
-        }
-    }
-
-    /**
      * Override if the PiP should always use a fade-in animation during PiP entry.
      *
      * @return true if the mOneShotAnimationType should always be
@@ -733,26 +712,17 @@
             return;
         }
 
-        if (mOneShotAnimationType == ANIM_TYPE_ALPHA
-                && SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
-                > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: Alpha animation is expired. Use bounds animation.", TAG);
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
-        }
-
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             // For Shell transition, we will animate the window in PipTransition#startAnimation
             // instead of #onTaskAppeared.
             return;
         }
 
-        if (shouldAlwaysFadeIn()) {
-            mOneShotAnimationType = ANIM_TYPE_ALPHA;
-        }
-
+        final int animationType = shouldAlwaysFadeIn()
+                ? ANIM_TYPE_ALPHA
+                : mPipAnimationController.takeOneShotEnterAnimationType();
         if (mWaitForFixedRotation) {
-            onTaskAppearedWithFixedRotation();
+            onTaskAppearedWithFixedRotation(animationType);
             return;
         }
 
@@ -763,7 +733,7 @@
         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
 
-        if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+        if (animationType == ANIM_TYPE_BOUNDS) {
             if (!shouldAttachMenuEarly()) {
                 mPipMenuController.attach(mLeash);
             }
@@ -773,16 +743,15 @@
                     sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration,
                     null /* updateBoundsCallback */);
             mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
-        } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        } else if (animationType == ANIM_TYPE_ALPHA) {
             enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
         } else {
-            throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType);
+            throw new RuntimeException("Unrecognized animation type: " + animationType);
         }
     }
 
-    private void onTaskAppearedWithFixedRotation() {
-        if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+    private void onTaskAppearedWithFixedRotation(int animationType) {
+        if (animationType == ANIM_TYPE_ALPHA) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: Defer entering PiP alpha animation, fixed rotation is ongoing", TAG);
             // If deferred, hside the surface till fixed rotation is completed.
@@ -791,7 +760,6 @@
             tx.setAlpha(mLeash, 0f);
             tx.show(mLeash);
             tx.apply();
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
             return;
         }
         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
@@ -1895,7 +1863,6 @@
                 + " binder=" + (mToken != null ? mToken.asBinder() : null));
         pw.println(innerPrefix + "mLeash=" + mLeash);
         pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState());
-        pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType);
         pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
     }
 
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 4a76a50..b743140 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
@@ -87,7 +87,7 @@
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
     private final Optional<SplitScreenController> mSplitScreenOptional;
-    private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+    private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS;
     private Transitions.TransitionFinishCallback mFinishCallback;
     private SurfaceControl.Transaction mFinishTransaction;
     private final Rect mExitDestinationBounds = new Rect();
@@ -133,20 +133,6 @@
     }
 
     @Override
-    public void setIsFullAnimation(boolean isFullAnimation) {
-        setOneShotAnimationType(isFullAnimation ? ANIM_TYPE_BOUNDS : ANIM_TYPE_ALPHA);
-    }
-
-    /**
-     * Sets the preferred animation type for one time.
-     * This is typically used to set the animation type to
-     * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
-     */
-    private void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
-        mOneShotAnimationType = animationType;
-    }
-
-    @Override
     public void startExitTransition(int type, WindowContainerTransaction out,
             @Nullable Rect destinationBounds) {
         if (destinationBounds != null) {
@@ -288,7 +274,10 @@
         if (!requestHasPipEnter(request)) {
             throw new IllegalStateException("Called PiP augmentRequest when request has no PiP");
         }
-        if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        mEnterAnimationType = mPipOrganizer.shouldAlwaysFadeIn()
+                ? ANIM_TYPE_ALPHA
+                : mPipAnimationController.takeOneShotEnterAnimationType();
+        if (mEnterAnimationType == ANIM_TYPE_ALPHA) {
             mRequestedEnterTransition = transition;
             mRequestedEnterTask = request.getTriggerTask().token;
             outWCT.setActivityWindowingMode(request.getTriggerTask().token,
@@ -308,7 +297,7 @@
     @Override
     public boolean handleRotateDisplay(int startRotation, int endRotation,
             WindowContainerTransaction wct) {
-        if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        if (mRequestedEnterTransition != null && mEnterAnimationType == ANIM_TYPE_ALPHA) {
             // A fade-in was requested but not-yet started. In this case, just recalculate the
             // initial state under the new rotation.
             int rotationDelta = deltaRotation(startRotation, endRotation);
@@ -760,7 +749,6 @@
         if (taskInfo.pictureInPictureParams != null
                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()
                 && mPipTransitionState.getInSwipePipToHomeTransition()) {
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
             final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
             startTransaction.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9])
                     .setPosition(leash, destinationBounds.left, destinationBounds.top)
@@ -796,17 +784,16 @@
             startTransaction.setMatrix(leash, tmpTransform, new float[9]);
         }
 
-        if (mPipOrganizer.shouldAlwaysFadeIn()) {
-            mOneShotAnimationType = ANIM_TYPE_ALPHA;
-        }
-
-        if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        final int enterAnimationType = mEnterAnimationType;
+        if (enterAnimationType == ANIM_TYPE_ALPHA) {
+            // Restore to default type.
+            mEnterAnimationType = ANIM_TYPE_BOUNDS;
             startTransaction.setAlpha(leash, 0f);
         }
         startTransaction.apply();
 
         PipAnimationController.PipTransitionAnimator animator;
-        if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+        if (enterAnimationType == ANIM_TYPE_BOUNDS) {
             animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
                     currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
                     0 /* startingAngle */, rotationDelta);
@@ -829,13 +816,11 @@
                     animator.setColorContentOverlay(mContext);
                 }
             }
-        } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        } else if (enterAnimationType == ANIM_TYPE_ALPHA) {
             animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
                     0f, 1f);
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
         } else {
-            throw new RuntimeException("Unrecognized animation type: "
-                    + mOneShotAnimationType);
+            throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
         }
         animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
                 .setPipAnimationCallback(mPipAnimationCallback)
@@ -897,7 +882,7 @@
                         .setWindowCrop(leash, endBounds.width(), endBounds.height());
             }
         }
-        mSplitScreenOptional.get().finishEnterSplitScreen(startTransaction);
+        mSplitScreenOptional.get().finishEnterSplitScreen(finishTransaction);
         startTransaction.apply();
 
         mPipOrganizer.onExitPipFinished(taskInfo);
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 f51e247..7979ce7 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
@@ -105,15 +105,6 @@
     }
 
     /**
-     * Called to inform the transition that the animation should start with the assumption that
-     * PiP is not animating from its original bounds, but rather a continuation of another
-     * animation. For example, gesture navigation would first fade out the PiP activity, and the
-     * transition should be responsible to animate in (such as fade in) the PiP.
-     */
-    public void setIsFullAnimation(boolean isFullAnimation) {
-    }
-
-    /**
      * Called when the Shell wants to start an exit Pip transition/animation.
      */
     public void startExitTransition(int type, WindowContainerTransaction out,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 463ad77..b0bb14b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -968,12 +968,6 @@
         mPipBoundsState.setShelfVisibility(visible, shelfHeight);
     }
 
-    private void setPinnedStackAnimationType(int animationType) {
-        mPipTaskOrganizer.setOneShotAnimationType(animationType);
-        mPipTransitionController.setIsFullAnimation(
-                animationType == PipAnimationController.ANIM_TYPE_BOUNDS);
-    }
-
     @VisibleForTesting
     void setPinnedStackAnimationListener(PipAnimationListener callback) {
         mPinnedStackAnimationRecentsCallback = callback;
@@ -1337,7 +1331,8 @@
         @Override
         public void setPipAnimationTypeToAlpha() {
             executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha",
-                    (controller) -> controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA));
+                    (controller) -> controller.mPipAnimationController.setOneShotEnterAnimationType(
+                            ANIM_TYPE_ALPHA));
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 81e118a..f819bee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -129,9 +129,10 @@
     /**
      * Start a pair of intents in one transition.
      */
-    oneway void startIntents(in PendingIntent pendingIntent1, in Bundle options1,
-            in PendingIntent pendingIntent2, in Bundle options2, int splitPosition,
-            float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
+    oneway void startIntents(in PendingIntent pendingIntent1, in ShortcutInfo shortcutInfo1,
+            in Bundle options1, in PendingIntent pendingIntent2, in ShortcutInfo shortcutInfo2,
+            in Bundle options2, int splitPosition, float splitRatio,
+            in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
 
     /**
      * Blocking call that notifies and gets additional split-screen targets when entering
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 7d5ab84..2cd16be 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
@@ -626,6 +626,35 @@
                 splitPosition, splitRatio, adapter, instanceId);
     }
 
+    private void startIntents(PendingIntent pendingIntent1,
+            @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+            PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+            @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+            @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        Intent fillInIntent1 = null;
+        Intent fillInIntent2 = null;
+        final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
+        final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
+        if (samePackage(packageName1, packageName2)) {
+            if (supportMultiInstancesSplit(packageName1)) {
+                fillInIntent1 = new Intent();
+                fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                fillInIntent2 = new Intent();
+                fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+            } else {
+                pendingIntent2 = null;
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Cancel entering split as not supporting multi-instances");
+                Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+                        Toast.LENGTH_SHORT).show();
+            }
+        }
+        mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, options1,
+                pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio,
+                remoteTransition, instanceId);
+    }
+
     @Override
     public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
             @SplitPosition int position, @Nullable Bundle options) {
@@ -1066,11 +1095,17 @@
         }
 
         @Override
-        public void startIntents(PendingIntent pendingIntent1, @Nullable Bundle options1,
-                PendingIntent pendingIntent2, @Nullable Bundle options2,
+        public void startIntents(PendingIntent pendingIntent1, @Nullable ShortcutInfo shortcutInfo1,
+                @Nullable Bundle options1, PendingIntent pendingIntent2,
+                @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
                 @SplitPosition int splitPosition, float splitRatio,
                 @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
-            // TODO(b/259368992): To be implemented.
+            executeRemoteCallWithTaskPermission(mController, "startIntents",
+                    (controller) ->
+                            controller.startIntents(pendingIntent1, shortcutInfo1,
+                                    options1, pendingIntent2, shortcutInfo2, options2,
+                                    splitPosition, splitRatio, remoteTransition, instanceId)
+            );
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 22800ad..8b890bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -22,6 +22,7 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
@@ -155,8 +156,10 @@
             }
             boolean isRootOrSplitSideRoot = change.getParent() == null
                     || topRoot.equals(change.getParent());
-            // For enter or exit, we only want to animate the side roots but not the top-root.
-            if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer())) {
+            boolean isDivider = change.getFlags() == FLAG_IS_DIVIDER_BAR;
+            // For enter or exit, we only want to animate side roots and the divider but not the
+            // top-root.
+            if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer()) || isDivider) {
                 continue;
             }
 
@@ -165,6 +168,10 @@
                 t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
                 t.setWindowCrop(leash, change.getEndAbsBounds().width(),
                         change.getEndAbsBounds().height());
+            } else if (isDivider) {
+                t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
+                t.setLayer(leash, Integer.MAX_VALUE);
+                t.show(leash);
             }
             boolean isOpening = isOpeningTransition(info);
             if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
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 dd91a37..ce5a2af6 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
@@ -682,6 +682,46 @@
         setEnterInstanceId(instanceId);
     }
 
+    void startIntents(PendingIntent pendingIntent1, Intent fillInIntent1,
+            @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+            PendingIntent pendingIntent2, Intent fillInIntent2,
+            @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
+            @SplitPosition int splitPosition, float splitRatio,
+            @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        if (!mMainStage.isActive()) {
+            // Build a request WCT that will launch both apps such that task 0 is on the main stage
+            // while task 1 is on the side stage.
+            mMainStage.activate(wct, false /* reparent */);
+        }
+
+        prepareEvictChildTasksIfSplitActive(wct);
+        mSplitLayout.setDivideRatio(splitRatio);
+        updateWindowBounds(mSplitLayout, wct);
+        wct.reorder(mRootTaskInfo.token, true);
+        setRootForceTranslucent(false, wct);
+
+        setSideStagePosition(splitPosition, wct);
+        options1 = options1 != null ? options1 : new Bundle();
+        addActivityOptions(options1, mSideStage);
+        if (shortcutInfo1 != null) {
+            wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+        } else {
+            wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+        }
+        options2 = options2 != null ? options2 : new Bundle();
+        addActivityOptions(options2, mMainStage);
+        if (shortcutInfo2 != null) {
+            wct.startShortcut(mContext.getPackageName(), shortcutInfo2, options2);
+        } else {
+            wct.sendPendingIntent(pendingIntent2, fillInIntent2, options2);
+        }
+
+        mSplitTransitions.startEnterTransition(
+                TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null, null);
+        setEnterInstanceId(instanceId);
+    }
+
     /** Starts a pair of tasks using legacy transition. */
     void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
             int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
@@ -690,6 +730,10 @@
         if (options1 == null) options1 = new Bundle();
         if (taskId2 == INVALID_TASK_ID) {
             // Launching a solo task.
+            // Exit split first if this task under split roots.
+            if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
+                exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+            }
             ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
             activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
             options1 = activityOptions.toBundle();
@@ -891,10 +935,6 @@
             mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct);
         }
 
-        mSyncQueue.runInSync(t -> {
-            setDividerVisibility(true, t);
-        });
-
         setEnterInstanceId(instanceId);
     }
 
@@ -933,6 +973,7 @@
             @Override
             public void onAnimationCancelled(boolean isKeyguardOccluded) {
                 onRemoteAnimationFinishedOrCancelled(evictWct);
+                setDividerVisibility(true, null);
                 try {
                     adapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
                 } catch (RemoteException e) {
@@ -973,6 +1014,7 @@
                             t.setPosition(apps[i].leash, 0, 0);
                         }
                     }
+                    setDividerVisibility(true, t);
                     t.apply();
 
                     IRemoteAnimationFinishedCallback wrapCallback =
@@ -1463,6 +1505,10 @@
     void finishEnterSplitScreen(SurfaceControl.Transaction t) {
         mSplitLayout.init();
         setDividerVisibility(true, t);
+        // Ensure divider surface are re-parented back into the hierarchy at the end of the
+        // transition. See Transition#buildFinishTransaction for more detail.
+        t.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash);
+
         updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
         t.show(mRootTaskLeash);
         setSplitsVisible(true);
@@ -1772,6 +1818,8 @@
         setDividerVisibility(mainStageVisible, null);
     }
 
+    // Set divider visibility flag and try to apply it, the param transaction is used to apply.
+    // See applyDividerVisibility for more detail.
     private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) {
         if (visible == mDividerVisible) {
             return;
@@ -1798,14 +1846,13 @@
             return;
         }
 
-        if (t != null) {
-            applyDividerVisibility(t);
-        } else {
-            mSyncQueue.runInSync(transaction -> applyDividerVisibility(transaction));
-        }
+        applyDividerVisibility(t);
     }
 
-    private void applyDividerVisibility(SurfaceControl.Transaction t) {
+    // Apply divider visibility by current visibility flag. If param transaction is non-null, it
+    // will apply by that transaction, if it is null and visible, it will run a fade-in animation,
+    // otherwise hide immediately.
+    private void applyDividerVisibility(@Nullable SurfaceControl.Transaction t) {
         final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
         if (dividerLeash == null) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
@@ -1822,7 +1869,12 @@
             mDividerFadeInAnimator.cancel();
         }
 
-        if (mDividerVisible) {
+        mSplitLayout.getRefDividerBounds(mTempRect1);
+        if (t != null) {
+            t.setVisibility(dividerLeash, mDividerVisible);
+            t.setLayer(dividerLeash, Integer.MAX_VALUE);
+            t.setPosition(dividerLeash, mTempRect1.left, mTempRect1.top);
+        } else if (mDividerVisible) {
             final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
             mDividerFadeInAnimator = ValueAnimator.ofFloat(0f, 1f);
             mDividerFadeInAnimator.addUpdateListener(animation -> {
@@ -1862,7 +1914,10 @@
 
             mDividerFadeInAnimator.start();
         } else {
-            t.hide(dividerLeash);
+            final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+            transaction.hide(dividerLeash);
+            transaction.apply();
+            mTransactionPool.release(transaction);
         }
     }
 
@@ -2446,7 +2501,7 @@
         }
 
         finishEnterSplitScreen(finishT);
-        addDividerBarToTransition(info, finishT, true /* show */);
+        addDividerBarToTransition(info, true /* show */);
         return true;
     }
 
@@ -2589,7 +2644,7 @@
         if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) {
             // TODO: Have a proper remote for this. Until then, though, reset state and use the
             //       normal animation stuff (which falls back to the normal launcher remote).
-            t.hide(mSplitLayout.getDividerLeash());
+            setDividerVisibility(false, t);
             mSplitLayout.release(t);
             mSplitTransitions.mPendingDismiss = null;
             return false;
@@ -2607,7 +2662,7 @@
                     });
         }
 
-        addDividerBarToTransition(info, finishT, false /* show */);
+        addDividerBarToTransition(info, false /* show */);
         return true;
     }
 
@@ -2648,11 +2703,11 @@
         logExit(EXIT_REASON_UNKNOWN);
     }
 
-    private void addDividerBarToTransition(@NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction finishT, boolean show) {
+    private void addDividerBarToTransition(@NonNull TransitionInfo info, boolean show) {
         final SurfaceControl leash = mSplitLayout.getDividerLeash();
         final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
         mSplitLayout.getRefDividerBounds(mTempRect1);
+        barChange.setParent(mRootTaskInfo.token);
         barChange.setStartAbsBounds(mTempRect1);
         barChange.setEndAbsBounds(mTempRect1);
         barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
@@ -2660,15 +2715,6 @@
         // Technically this should be order-0, but this is running after layer assignment
         // and it's a special case, so just add to end.
         info.addChange(barChange);
-
-        if (show) {
-            finishT.setLayer(leash, Integer.MAX_VALUE);
-            finishT.setPosition(leash, mTempRect1.left, mTempRect1.top);
-            finishT.show(leash);
-            // Ensure divider surface are re-parented back into the hierarchy at the end of the
-            // transition. See Transition#buildFinishTransaction for more detail.
-            finishT.reparent(leash, mRootTaskLeash);
-        }
     }
 
     RemoteAnimationTarget getDividerBarLegacyTarget() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index fa4de16..bdb7d44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -143,6 +143,10 @@
     /** Transition type to fullscreen from desktop mode. */
     public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
 
+    /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */
+    public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE =
+            WindowManager.TRANSIT_FIRST_CUSTOM + 13;
+
     private final WindowOrganizer mOrganizer;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 060dc4e..dfde7e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -110,19 +110,11 @@
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
-        final int outsetLeftId = R.dimen.freeform_resize_handle;
-        final int outsetTopId = R.dimen.freeform_resize_handle;
-        final int outsetRightId = R.dimen.freeform_resize_handle;
-        final int outsetBottomId = R.dimen.freeform_resize_handle;
-
         mRelayoutParams.reset();
         mRelayoutParams.mRunningTaskInfo = taskInfo;
         mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
         mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
-        if (isDragResizeable) {
-            mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
-        }
 
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index afc573e..5226eee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -34,6 +34,7 @@
 import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.os.Handler;
@@ -557,8 +558,10 @@
                         mDragToDesktopAnimationStarted = false;
                         return;
                     } else if (mDragToDesktopAnimationStarted) {
-                        mDesktopTasksController.ifPresent(c ->
-                                c.moveToFullscreen(relevantDecor.mTaskInfo));
+                        Point startPosition = new Point((int) ev.getX(), (int) ev.getY());
+                        mDesktopTasksController.ifPresent(
+                                c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
+                                        startPosition));
                         mDragToDesktopAnimationStarted = false;
                         return;
                     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index f9c0e60..67e99d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -42,6 +42,7 @@
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.TextView;
+import android.window.SurfaceSyncGroup;
 import android.window.WindowContainerTransaction;
 
 import com.android.launcher3.icons.IconProvider;
@@ -208,11 +209,6 @@
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
-        final int outsetLeftId = R.dimen.freeform_resize_handle;
-        final int outsetTopId = R.dimen.freeform_resize_handle;
-        final int outsetRightId = R.dimen.freeform_resize_handle;
-        final int outsetBottomId = R.dimen.freeform_resize_handle;
-
         final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
                 taskInfo.getWindowingMode());
         mRelayoutParams.reset();
@@ -220,9 +216,6 @@
         mRelayoutParams.mLayoutResId = windowDecorLayoutId;
         mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
-        if (isDragResizeable) {
-            mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
-        }
 
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -319,51 +312,50 @@
      * Create and display handle menu window
      */
     void createHandleMenu() {
+        final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         updateHandleMenuPillPositions();
 
-        createAppInfoPill(t);
+        createAppInfoPill(t, ssg);
 
         // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
         final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
         if (shouldShowWindowingPill) {
-            createWindowingPill(t);
+            createWindowingPill(t, ssg);
         }
 
-        createMoreActionsPill(t);
+        createMoreActionsPill(t, ssg);
 
-        mSyncQueue.runInSync(transaction -> {
-            transaction.merge(t);
-            t.close();
-        });
+        ssg.addTransaction(t);
+        ssg.markSyncReady();
         setupHandleMenu(shouldShowWindowingPill);
     }
 
-    private void createAppInfoPill(SurfaceControl.Transaction t) {
+    private void createAppInfoPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
         final int x = (int) mHandleMenuAppInfoPillPosition.x;
         final int y = (int) mHandleMenuAppInfoPillPosition.y;
         mHandleMenuAppInfoPill = addWindow(
                 R.layout.desktop_mode_window_decor_handle_menu_app_info_pill,
                 "Menu's app info pill",
-                t, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
+                t, ssg, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
     }
 
-    private void createWindowingPill(SurfaceControl.Transaction t) {
+    private void createWindowingPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
         final int x = (int) mHandleMenuWindowingPillPosition.x;
         final int y = (int) mHandleMenuWindowingPillPosition.y;
         mHandleMenuWindowingPill = addWindow(
                 R.layout.desktop_mode_window_decor_handle_menu_windowing_pill,
                 "Menu's windowing pill",
-                t, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
+                t, ssg, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
     }
 
-    private void createMoreActionsPill(SurfaceControl.Transaction t) {
+    private void createMoreActionsPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
         final int x = (int) mHandleMenuMoreActionsPillPosition.x;
         final int y = (int) mHandleMenuMoreActionsPillPosition.y;
         mHandleMenuMoreActionsPill = addWindow(
                 R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill,
                 "Menu's more actions pill",
-                t, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
+                t, ssg, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
     }
 
     private void setupHandleMenu(boolean windowingPillShown) {
@@ -424,13 +416,12 @@
         if (mRelayoutParams.mLayoutResId
                 == R.layout.desktop_mode_app_controls_window_decor) {
             // Align the handle menu to the left of the caption.
-            menuX = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX + mMarginMenuStart;
-            menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuTop;
+            menuX = mRelayoutParams.mCaptionX + mMarginMenuStart;
+            menuY = mRelayoutParams.mCaptionY + mMarginMenuTop;
         } else {
             // Position the handle menu at the center of the caption.
-            menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2)
-                    - mResult.mDecorContainerOffsetX;
-            menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuStart;
+            menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2);
+            menuY = mRelayoutParams.mCaptionY + mMarginMenuStart;
         }
 
         // App Info pill setup.
@@ -497,23 +488,18 @@
 
         final boolean pointInAppInfoPill = pointInView(
                 mHandleMenuAppInfoPill.mWindowViewHost.getView(),
-                inputPoint.x - mHandleMenuAppInfoPillPosition.x - mResult.mDecorContainerOffsetX,
-                inputPoint.y - mHandleMenuAppInfoPillPosition.y
-                        - mResult.mDecorContainerOffsetY);
+                inputPoint.x - mHandleMenuAppInfoPillPosition.x,
+                inputPoint.y - mHandleMenuAppInfoPillPosition.y);
         boolean pointInWindowingPill = false;
         if (mHandleMenuWindowingPill != null) {
             pointInWindowingPill = pointInView(mHandleMenuWindowingPill.mWindowViewHost.getView(),
-                    inputPoint.x - mHandleMenuWindowingPillPosition.x
-                            - mResult.mDecorContainerOffsetX,
-                    inputPoint.y - mHandleMenuWindowingPillPosition.y
-                            - mResult.mDecorContainerOffsetY);
+                    inputPoint.x - mHandleMenuWindowingPillPosition.x,
+                    inputPoint.y - mHandleMenuWindowingPillPosition.y);
         }
         final boolean pointInMoreActionsPill = pointInView(
                 mHandleMenuMoreActionsPill.mWindowViewHost.getView(),
-                inputPoint.x - mHandleMenuMoreActionsPillPosition.x
-                        - mResult.mDecorContainerOffsetX,
-                inputPoint.y - mHandleMenuMoreActionsPillPosition.y
-                        - mResult.mDecorContainerOffsetY);
+                inputPoint.x - mHandleMenuMoreActionsPillPosition.x,
+                inputPoint.y - mHandleMenuMoreActionsPillPosition.y);
         if (!pointInAppInfoPill && !pointInWindowingPill
                 && !pointInMoreActionsPill && !pointInOpenMenuButton) {
             closeHandleMenu();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8cb575c..d5437c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -64,8 +64,8 @@
     private final TaskResizeInputEventReceiver mInputEventReceiver;
     private final DragPositioningCallback mCallback;
 
-    private int mWidth;
-    private int mHeight;
+    private int mTaskWidth;
+    private int mTaskHeight;
     private int mResizeHandleThickness;
     private int mCornerSize;
 
@@ -128,78 +128,84 @@
      * This is also used to update the touch regions of this handler every event dispatched here is
      * a potential resize request.
      *
-     * @param width The width of the drag resize handler in pixels, including resize handle
-     *              thickness. That is task width + 2 * resize handle thickness.
-     * @param height The height of the drag resize handler in pixels, including resize handle
-     *               thickness. That is task height + 2 * resize handle thickness.
+     * @param taskWidth The width of the task.
+     * @param taskHeight The height of the task.
      * @param resizeHandleThickness The thickness of the resize handle in pixels.
      * @param cornerSize The size of the resize handle centered in each corner.
      * @param touchSlop The distance in pixels user has to drag with touch for it to register as
      *                  a resize action.
      */
-    void setGeometry(int width, int height, int resizeHandleThickness, int cornerSize,
+    void setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize,
             int touchSlop) {
-        if (mWidth == width && mHeight == height
+        if (mTaskWidth == taskWidth && mTaskHeight == taskHeight
                 && mResizeHandleThickness == resizeHandleThickness
                 && mCornerSize == cornerSize) {
             return;
         }
 
-        mWidth = width;
-        mHeight = height;
+        mTaskWidth = taskWidth;
+        mTaskHeight = taskHeight;
         mResizeHandleThickness = resizeHandleThickness;
         mCornerSize = cornerSize;
         mDragDetector.setTouchSlop(touchSlop);
 
         Region touchRegion = new Region();
-        final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
+        final Rect topInputBounds = new Rect(
+                -mResizeHandleThickness,
+                -mResizeHandleThickness,
+                mTaskWidth + mResizeHandleThickness,
+                0);
         touchRegion.union(topInputBounds);
 
-        final Rect leftInputBounds = new Rect(0, mResizeHandleThickness,
-                mResizeHandleThickness, mHeight - mResizeHandleThickness);
+        final Rect leftInputBounds = new Rect(
+                -mResizeHandleThickness,
+                0,
+                0,
+                mTaskHeight);
         touchRegion.union(leftInputBounds);
 
         final Rect rightInputBounds = new Rect(
-                mWidth - mResizeHandleThickness, mResizeHandleThickness,
-                mWidth, mHeight - mResizeHandleThickness);
+                mTaskWidth,
+                0,
+                mTaskWidth + mResizeHandleThickness,
+                mTaskHeight);
         touchRegion.union(rightInputBounds);
 
-        final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness,
-                mWidth, mHeight);
+        final Rect bottomInputBounds = new Rect(
+                -mResizeHandleThickness,
+                mTaskHeight,
+                mTaskWidth + mResizeHandleThickness,
+                mTaskHeight + mResizeHandleThickness);
         touchRegion.union(bottomInputBounds);
 
         // Set up touch areas in each corner.
         int cornerRadius = mCornerSize / 2;
         mLeftTopCornerBounds = new Rect(
-                mResizeHandleThickness - cornerRadius,
-                mResizeHandleThickness - cornerRadius,
-                mResizeHandleThickness + cornerRadius,
-                mResizeHandleThickness + cornerRadius
-        );
+                -cornerRadius,
+                -cornerRadius,
+                cornerRadius,
+                cornerRadius);
         touchRegion.union(mLeftTopCornerBounds);
 
         mRightTopCornerBounds = new Rect(
-                mWidth - mResizeHandleThickness - cornerRadius,
-                mResizeHandleThickness - cornerRadius,
-                mWidth - mResizeHandleThickness + cornerRadius,
-                mResizeHandleThickness + cornerRadius
-        );
+                mTaskWidth - cornerRadius,
+                -cornerRadius,
+                mTaskWidth + cornerRadius,
+                cornerRadius);
         touchRegion.union(mRightTopCornerBounds);
 
         mLeftBottomCornerBounds = new Rect(
-                mResizeHandleThickness - cornerRadius,
-                mHeight - mResizeHandleThickness - cornerRadius,
-                mResizeHandleThickness + cornerRadius,
-                mHeight - mResizeHandleThickness + cornerRadius
-        );
+                -cornerRadius,
+                mTaskHeight - cornerRadius,
+                cornerRadius,
+                mTaskHeight + cornerRadius);
         touchRegion.union(mLeftBottomCornerBounds);
 
         mRightBottomCornerBounds = new Rect(
-                mWidth - mResizeHandleThickness - cornerRadius,
-                mHeight - mResizeHandleThickness - cornerRadius,
-                mWidth - mResizeHandleThickness + cornerRadius,
-                mHeight - mResizeHandleThickness + cornerRadius
-        );
+                mTaskWidth - cornerRadius,
+                mTaskHeight - cornerRadius,
+                mTaskWidth + cornerRadius,
+                mTaskHeight + cornerRadius);
         touchRegion.union(mRightBottomCornerBounds);
 
         try {
@@ -358,16 +364,16 @@
         @TaskPositioner.CtrlType
         private int calculateResizeHandlesCtrlType(float x, float y) {
             int ctrlType = 0;
-            if (x < mResizeHandleThickness) {
+            if (x < 0) {
                 ctrlType |= TaskPositioner.CTRL_TYPE_LEFT;
             }
-            if (x > mWidth - mResizeHandleThickness) {
+            if (x > mTaskWidth) {
                 ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT;
             }
-            if (y < mResizeHandleThickness) {
+            if (y < 0) {
                 ctrlType |= TaskPositioner.CTRL_TYPE_TOP;
             }
-            if (y > mHeight - mResizeHandleThickness) {
+            if (y > mTaskHeight) {
                 ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM;
             }
             return ctrlType;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 4ebd09f..19a3182 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -34,6 +34,7 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowlessWindowManager;
+import android.window.SurfaceSyncGroup;
 import android.window.TaskConstants;
 import android.window.WindowContainerTransaction;
 
@@ -98,7 +99,6 @@
 
     private final Binder mOwner = new Binder();
     private final Rect mCaptionInsetsRect = new Rect();
-    private final Rect mTaskSurfaceCrop = new Rect();
     private final float[] mTmpColor = new float[3];
 
     WindowDecoration(
@@ -193,13 +193,13 @@
             mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
             if (params.mLayoutResId != 0) {
                 outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
-                                .inflate(params.mLayoutResId, null);
+                        .inflate(params.mLayoutResId, null);
             }
         }
 
         if (outResult.mRootView == null) {
             outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
-                            .inflate(params.mLayoutResId , null);
+                    .inflate(params.mLayoutResId, null);
         }
 
         // DecorationContainerSurface
@@ -218,21 +218,14 @@
 
         final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
         final Resources resources = mDecorWindowContext.getResources();
-        outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
-        outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
-        outResult.mWidth = taskBounds.width()
-                + loadDimensionPixelSize(resources, params.mOutsetRightId)
-                - outResult.mDecorContainerOffsetX;
-        outResult.mHeight = taskBounds.height()
-                + loadDimensionPixelSize(resources, params.mOutsetBottomId)
-                - outResult.mDecorContainerOffsetY;
-        startT.setPosition(
-                        mDecorationContainerSurface,
-                        outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
-                .setWindowCrop(mDecorationContainerSurface,
-                        outResult.mWidth, outResult.mHeight)
+        outResult.mWidth = taskBounds.width();
+        outResult.mHeight = taskBounds.height();
+        startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
                 .show(mDecorationContainerSurface);
 
+        // TODO(b/270202228): This surface can be removed. Instead, use
+        //  |mDecorationContainerSurface| to set the background now that it no longer has outsets
+        //  and its crop is set to the task bounds.
         // TaskBackgroundSurface
         if (mTaskBackgroundSurface == null) {
             final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
@@ -250,8 +243,7 @@
         mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
         mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
         mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
-        startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(),
-                        taskBounds.height())
+        startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
                 .setShadowRadius(mTaskBackgroundSurface, shadowRadius)
                 .setColor(mTaskBackgroundSurface, mTmpColor)
                 .show(mTaskBackgroundSurface);
@@ -269,11 +261,7 @@
         final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
         final int captionWidth = taskBounds.width();
 
-        startT.setPosition(
-                        mCaptionContainerSurface,
-                        -outResult.mDecorContainerOffsetX + params.mCaptionX,
-                        -outResult.mDecorContainerOffsetY + params.mCaptionY)
-                .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
+        startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
                 .show(mCaptionContainerSurface);
 
         if (mCaptionWindowManager == null) {
@@ -314,14 +302,9 @@
 
         // Task surface itself
         Point taskPosition = mTaskInfo.positionInParent;
-        mTaskSurfaceCrop.set(
-                outResult.mDecorContainerOffsetX,
-                outResult.mDecorContainerOffsetY,
-                outResult.mWidth + outResult.mDecorContainerOffsetX,
-                outResult.mHeight + outResult.mDecorContainerOffsetY);
         startT.show(mTaskSurface);
         finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
-                .setCrop(mTaskSurface, mTaskSurfaceCrop);
+                .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
     }
 
     /**
@@ -400,18 +383,20 @@
     /**
      * Create a window associated with this WindowDecoration.
      * Note that subclass must dispose of this when the task is hidden/closed.
-     * @param layoutId layout to make the window from
-     * @param t the transaction to apply
-     * @param xPos x position of new window
-     * @param yPos y position of new window
-     * @param width width of new window
-     * @param height height of new window
+     *
+     * @param layoutId     layout to make the window from
+     * @param t            the transaction to apply
+     * @param xPos         x position of new window
+     * @param yPos         y position of new window
+     * @param width        width of new window
+     * @param height       height of new window
      * @param shadowRadius radius of the shadow of the new window
      * @param cornerRadius radius of the corners of the new window
      * @return the {@link AdditionalWindow} that was added.
      */
     AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t,
-            int xPos, int yPos, int width, int height, int shadowRadius, int cornerRadius) {
+            SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height, int shadowRadius,
+            int cornerRadius) {
         final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
         SurfaceControl windowSurfaceControl = builder
                 .setName(namePrefix + " of Task=" + mTaskInfo.taskId)
@@ -435,49 +420,27 @@
                 windowSurfaceControl, null /* hostInputToken */);
         SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
                 .create(mDecorWindowContext, mDisplay, windowManager);
-        viewHost.setView(v, lp);
+        ssg.add(viewHost.getSurfacePackage(), () -> viewHost.setView(v, lp));
         return new AdditionalWindow(windowSurfaceControl, viewHost,
                 mSurfaceControlTransactionSupplier);
     }
 
-    static class RelayoutParams{
+    static class RelayoutParams {
         RunningTaskInfo mRunningTaskInfo;
         int mLayoutResId;
         int mCaptionHeightId;
         int mCaptionWidthId;
         int mShadowRadiusId;
 
-        int mOutsetTopId;
-        int mOutsetBottomId;
-        int mOutsetLeftId;
-        int mOutsetRightId;
-
         int mCaptionX;
         int mCaptionY;
 
-        void setOutsets(int leftId, int topId, int rightId, int bottomId) {
-            mOutsetLeftId = leftId;
-            mOutsetTopId = topId;
-            mOutsetRightId = rightId;
-            mOutsetBottomId = bottomId;
-        }
-
-        void setCaptionPosition(int left, int top) {
-            mCaptionX = left;
-            mCaptionY = top;
-        }
-
         void reset() {
             mLayoutResId = Resources.ID_NULL;
             mCaptionHeightId = Resources.ID_NULL;
             mCaptionWidthId = Resources.ID_NULL;
             mShadowRadiusId = Resources.ID_NULL;
 
-            mOutsetTopId = Resources.ID_NULL;
-            mOutsetBottomId = Resources.ID_NULL;
-            mOutsetLeftId = Resources.ID_NULL;
-            mOutsetRightId = Resources.ID_NULL;
-
             mCaptionX = 0;
             mCaptionY = 0;
         }
@@ -487,14 +450,10 @@
         int mWidth;
         int mHeight;
         T mRootView;
-        int mDecorContainerOffsetX;
-        int mDecorContainerOffsetY;
 
         void reset() {
             mWidth = 0;
             mHeight = 0;
-            mDecorContainerOffsetX = 0;
-            mDecorContainerOffsetY = 0;
             mRootView = null;
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 67ca9a1..b6d92814 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -29,6 +29,7 @@
         <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
         <option name="teardown-command" value="settings delete system show_touches" />
         <option name="teardown-command" value="settings delete system pointer_location" />
+        <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 806bffe..d95c7a4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -166,6 +166,9 @@
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         doMotionEvent(MotionEvent.ACTION_MOVE, 0);
         mController.setTriggerBack(true);
+    }
+
+    private void releaseBackGesture() {
         doMotionEvent(MotionEvent.ACTION_UP, 0);
     }
 
@@ -201,6 +204,8 @@
                     .setOnBackNavigationDone(new RemoteCallback(result)));
             triggerBackGesture();
             simulateRemoteAnimationStart(type);
+            mShellExecutor.flushAll();
+            releaseBackGesture();
             simulateRemoteAnimationFinished();
             mShellExecutor.flushAll();
 
@@ -252,6 +257,7 @@
         createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false);
 
         triggerBackGesture();
+        releaseBackGesture();
 
         verify(mAppCallback, times(1)).onBackInvoked();
 
@@ -269,6 +275,8 @@
 
         triggerBackGesture();
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+        releaseBackGesture();
+
         // Check that back invocation is dispatched.
         verify(mAnimatorCallback).onBackInvoked();
         verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
@@ -308,6 +316,9 @@
 
         triggerBackGesture();
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+        mShellExecutor.flushAll();
+
+        releaseBackGesture();
 
         // Simulate transition timeout.
         mShellExecutor.flushAll();
@@ -369,6 +380,9 @@
             simulateRemoteAnimationStart(type);
             mShellExecutor.flushAll();
 
+            releaseBackGesture();
+            mShellExecutor.flushAll();
+
             assertTrue("Navigation Done callback not called for "
                     + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
             assertTrue("TriggerBack should have been true", result.mTriggerBack);
@@ -395,6 +409,8 @@
                 .setOnBackNavigationDone(new RemoteCallback(result)));
         triggerBackGesture();
         mShellExecutor.flushAll();
+        releaseBackGesture();
+        mShellExecutor.flushAll();
 
         assertTrue("Navigation Done callback not called for "
                 + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
@@ -458,9 +474,12 @@
 
     private void doMotionEvent(int actionDown, int coordinate) {
         mController.onMotionEvent(
-                coordinate, coordinate,
-                actionDown,
-                BackEvent.EDGE_LEFT);
+                /* touchX */ coordinate,
+                /* touchY */ coordinate,
+                /* velocityX = */ 0,
+                /* velocityY = */ 0,
+                /* keyAction */ actionDown,
+                /* swipeEdge */ BackEvent.EDGE_LEFT);
     }
 
     private void simulateRemoteAnimationStart(int type) throws RemoteException {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 3608474..874ef80 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell.back;
 
-import static android.window.BackEvent.EDGE_LEFT;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
@@ -48,12 +46,21 @@
     private CountDownLatch mTargetProgressCalled = new CountDownLatch(1);
     private Handler mMainThreadHandler;
 
+    private BackMotionEvent backMotionEventFrom(float touchX, float progress) {
+        return new BackMotionEvent(
+                /* touchX = */ touchX,
+                /* touchY = */ 0,
+                /* progress = */ progress,
+                /* velocityX = */ 0,
+                /* velocityY = */ 0,
+                /* swipeEdge = */ BackEvent.EDGE_LEFT,
+                /* departingAnimationTarget = */ null);
+    }
+
     @Before
     public void setUp() throws Exception {
         mMainThreadHandler = new Handler(Looper.getMainLooper());
-        final BackMotionEvent backEvent = new BackMotionEvent(
-                0, 0,
-                0, EDGE_LEFT, null);
+        final BackMotionEvent backEvent = backMotionEventFrom(0, 0);
         mMainThreadHandler.post(
                 () -> {
                     mProgressAnimator = new BackProgressAnimator();
@@ -63,9 +70,7 @@
 
     @Test
     public void testBackProgressed() throws InterruptedException {
-        final BackMotionEvent backEvent = new BackMotionEvent(
-                100, 0,
-                mTargetProgress, EDGE_LEFT, null);
+        final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
         mMainThreadHandler.post(
                 () -> mProgressAnimator.onBackProgressed(backEvent));
 
@@ -78,9 +83,7 @@
     @Test
     public void testBackCancelled() throws InterruptedException {
         // Give the animator some progress.
-        final BackMotionEvent backEvent = new BackMotionEvent(
-                100, 0,
-                mTargetProgress, EDGE_LEFT, null);
+        final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
         mMainThreadHandler.post(
                 () -> mProgressAnimator.onBackProgressed(backEvent));
         mTargetProgressCalled.await(1, TimeUnit.SECONDS);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
index ba9c159..d62e660 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -47,43 +47,45 @@
     public void generatesProgress_leftEdge() {
         mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
         float touchX = 10;
+        float velocityX = 0;
+        float velocityY = 0;
 
         // Pre-commit
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
 
         // Post-commit
         touchX += 100;
         mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
 
         // Cancel
         touchX -= 10;
         mTouchTracker.setTriggerBack(false);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Cancel more
         touchX -= 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Restart
         touchX += 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Restarted, but pre-commit
         float restartX = touchX;
         touchX += 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f);
 
         // Restarted, post-commit
         touchX += 10;
         mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
     }
 
@@ -91,43 +93,45 @@
     public void generatesProgress_rightEdge() {
         mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT);
         float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge
+        float velocityX = 0f;
+        float velocityY = 0f;
 
         // Pre-commit
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
 
         // Post-commit
         touchX -= 100;
         mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
 
         // Cancel
         touchX += 10;
         mTouchTracker.setTriggerBack(false);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Cancel more
         touchX += 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Restart
         touchX -= 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Restarted, but pre-commit
         float restartX = touchX;
         touchX -= 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f);
 
         // Restarted, post-commit
         touchX -= 10;
         mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 15bb10e..842c699 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -262,7 +262,8 @@
         DisplayLayout layout = new DisplayLayout(info,
                 mContext.getResources(), true, true);
         mPipDisplayLayoutState.setDisplayLayout(layout);
-        mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
+        doReturn(PipAnimationController.ANIM_TYPE_ALPHA).when(mMockPipAnimationController)
+                .takeOneShotEnterAnimationType();
         mPipTaskOrganizer.setSurfaceControlTransactionFactory(
                 MockSurfaceControlHelper::createMockSurfaceControlTransaction);
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index eda6fdc..e6219d1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -113,6 +113,7 @@
 
     private SurfaceSession mSurfaceSession = new SurfaceSession();
     private SurfaceControl mRootLeash;
+    private SurfaceControl mDividerLeash;
     private ActivityManager.RunningTaskInfo mRootTask;
     private StageCoordinator mStageCoordinator;
     private Transitions mTransitions;
@@ -129,12 +130,14 @@
                 mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
                 mMainExecutor, Optional.empty()));
+        mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build();
 
         when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
         when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
         when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds);
         when(mSplitLayout.isLandscape()).thenReturn(false);
         when(mSplitLayout.applyTaskChanges(any(), any(), any())).thenReturn(true);
+        when(mSplitLayout.getDividerLeash()).thenReturn(mDividerLeash);
 
         mRootTask = new TestRunningTaskInfoBuilder().build();
         mRootLeash = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index dfa3c10..38a519a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -49,6 +49,7 @@
 import android.view.ViewRootImpl;
 import android.view.WindowInsets;
 import android.view.WindowManager.LayoutParams;
+import android.window.SurfaceSyncGroup;
 import android.window.TaskConstants;
 import android.window.WindowContainerTransaction;
 
@@ -100,6 +101,8 @@
     private TestView mMockView;
     @Mock
     private WindowContainerTransaction mMockWindowContainerTransaction;
+    @Mock
+    private SurfaceSyncGroup mMockSurfaceSyncGroup;
 
     private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
             new ArrayList<>();
@@ -159,14 +162,8 @@
                 .setVisible(false)
                 .build();
         taskInfo.isFocused = false;
-        // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
-        // 64px.
+        // Density is 2. Shadow radius is 10px. Caption height is 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
 
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -213,14 +210,8 @@
                 .setVisible(true)
                 .build();
         taskInfo.isFocused = true;
-        // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
-        // 64px.
+        // Density is 2. Shadow radius is 10px. Caption height is 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
 
@@ -229,8 +220,7 @@
         verify(decorContainerSurfaceBuilder).setParent(taskSurface);
         verify(decorContainerSurfaceBuilder).setContainerLayer();
         verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
-        verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40);
-        verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220);
+        verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
 
         verify(taskBackgroundSurfaceBuilder).setParent(taskSurface);
         verify(taskBackgroundSurfaceBuilder).setEffectLayer();
@@ -244,7 +234,6 @@
 
         verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
         verify(captionContainerSurfaceBuilder).setContainerLayer();
-        verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
         verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
         verify(mMockSurfaceControlStartT).show(captionContainerSurface);
 
@@ -268,12 +257,12 @@
         verify(mMockSurfaceControlFinishT)
                 .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
         verify(mMockSurfaceControlFinishT)
-                .setCrop(taskSurface, new Rect(-20, -40, 360, 180));
+                .setWindowCrop(taskSurface, 300, 100);
         verify(mMockSurfaceControlStartT)
                 .show(taskSurface);
 
-        assertEquals(380, mRelayoutResult.mWidth);
-        assertEquals(220, mRelayoutResult.mHeight);
+        assertEquals(300, mRelayoutResult.mWidth);
+        assertEquals(100, mRelayoutResult.mHeight);
     }
 
     @Test
@@ -309,14 +298,8 @@
                 .setVisible(true)
                 .build();
         taskInfo.isFocused = true;
-        // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
-        // 64px.
+        // Density is 2. Shadow radius is 10px. Caption height is 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
 
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -419,11 +402,6 @@
                 .build();
         taskInfo.isFocused = true;
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
         windowDecor.relayout(taskInfo);
@@ -438,7 +416,7 @@
         verify(additionalWindowSurfaceBuilder).setContainerLayer();
         verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
         verify(additionalWindowSurfaceBuilder).build();
-        verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
+        verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0);
         final int width = WindowDecoration.loadDimensionPixelSize(
                 mContext.getResources(), mCaptionMenuWidthId);
         final int height = WindowDecoration.loadDimensionPixelSize(
@@ -496,11 +474,6 @@
                 .build();
         taskInfo.isFocused = true;
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
 
@@ -508,7 +481,6 @@
 
         verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
         verify(captionContainerSurfaceBuilder).setContainerLayer();
-        verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
         // Width of the captionContainerSurface should match the width of TASK_BOUNDS
         verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
         verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -584,9 +556,7 @@
             String name = "Test Window";
             WindowDecoration.AdditionalWindow additionalWindow =
                     addWindow(R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, name,
-                            mMockSurfaceControlAddWindowT,
-                            x - mRelayoutResult.mDecorContainerOffsetX,
-                            y - mRelayoutResult.mDecorContainerOffsetY,
+                            mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, x, y,
                             width, height, shadowRadius, cornerRadius);
             return additionalWindow;
         }
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 2a8cb42..c4d3f5c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -53,6 +53,8 @@
 }
 
 MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
+    bool wasSurfaceless = mEglManager.isCurrent(EGL_NO_SURFACE);
+
     // In case the surface was destroyed (e.g. a previous trimMemory call) we
     // need to recreate it here.
     if (mHardwareBuffer) {
@@ -65,6 +67,37 @@
     if (!mEglManager.makeCurrent(mEglSurface, &error)) {
         return MakeCurrentResult::AlreadyCurrent;
     }
+
+    // Make sure read/draw buffer state of default framebuffer is GL_BACK. Vendor implementations
+    // disagree on the draw/read buffer state if the default framebuffer transitions from a surface
+    // to EGL_NO_SURFACE and vice-versa. There was a related discussion within Khronos on this topic.
+    // See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13534.
+    // The discussion was not resolved with a clear consensus
+    if (error == 0 && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) {
+        GLint curReadFB = 0;
+        GLint curDrawFB = 0;
+        glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB);
+        glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curDrawFB);
+
+        GLint buffer = GL_NONE;
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        glGetIntegerv(GL_DRAW_BUFFER0, &buffer);
+        if (buffer == GL_NONE) {
+            const GLenum drawBuffer = GL_BACK;
+            glDrawBuffers(1, &drawBuffer);
+        }
+
+        glGetIntegerv(GL_READ_BUFFER, &buffer);
+        if (buffer == GL_NONE) {
+            glReadBuffer(GL_BACK);
+        }
+
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, curReadFB);
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, curDrawFB);
+
+        GL_CHECKPOINT(LOW);
+    }
+
     return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded;
 }
 
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 23611ef..7e9d44f 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -117,12 +117,8 @@
     // flush and submit all work to the gpu and wait for it to finish
     mGrContext->flushAndSubmit(/*syncCpu=*/true);
 
-    if (!Properties::isHighEndGfx && mode >= TrimLevel::MODERATE) {
-        mode = TrimLevel::COMPLETE;
-    }
-
     switch (mode) {
-        case TrimLevel::COMPLETE:
+        case TrimLevel::BACKGROUND:
             mGrContext->freeGpuResources();
             SkGraphics::PurgeAllCaches();
             mRenderThread.destroyRenderingContext();
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 1a3e54d..afa0a32 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -391,15 +391,6 @@
          * @hide
          */
         @Override
-        public void onError(int status) {
-            Slog.d(TAG, "onError()" + status);
-            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
-        }
-
-        /**
-         * @hide
-         */
-        @Override
         public void onRecognitionPaused() {
             Slog.d(TAG, "onRecognitionPaused()");
             mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE);
@@ -413,6 +404,42 @@
             Slog.d(TAG, "onRecognitionResumed()");
             mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
         }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void onPreempted() {
+            Slog.d(TAG, "onPreempted()");
+            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void onModuleDied() {
+            Slog.d(TAG, "onModuleDied()");
+            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void onResumeFailed(int status) {
+            Slog.d(TAG, "onResumeFailed()" + status);
+            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void onPauseFailed(int status) {
+            Slog.d(TAG, "onPauseFailed()" + status);
+            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
+        }
     }
 
     private class MyHandler extends Handler {
diff --git a/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
new file mode 100644
index 0000000..80bc5c0
--- /dev/null
+++ b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
@@ -0,0 +1,630 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.soundtrigger;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.hardware.soundtrigger.ConversionUtil;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.soundtrigger_middleware.IAcknowledgeEvent;
+import android.media.soundtrigger_middleware.IInjectGlobalEvent;
+import android.media.soundtrigger_middleware.IInjectModelEvent;
+import android.media.soundtrigger_middleware.IInjectRecognitionEvent;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.ISoundTriggerService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Used to inject/observe events when using a fake SoundTrigger HAL for test purposes.
+ * Created by {@link SoundTriggerManager#getInjection(Executor, GlobalCallback)}.
+ * Only one instance of this class is valid at any given time, old instances will be delivered
+ * {@link GlobalCallback#onPreempted()}.
+ * @hide
+ */
+@TestApi
+public final class SoundTriggerInstrumentation {
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private IInjectGlobalEvent mInjectGlobalEvent = null;
+
+    @GuardedBy("mLock")
+    private Map<IBinder, ModelSession> mModelSessionMap = new HashMap<>();
+    @GuardedBy("mLock")
+    private Map<IBinder, RecognitionSession> mRecognitionSessionMap = new HashMap<>();
+    @GuardedBy("mLock")
+    private IBinder mClientToken = null;
+
+    private final GlobalCallback mClientCallback;
+    private final Executor mGlobalCallbackExecutor;
+
+    /**
+     * Callback interface for un-sessioned events observed from the fake STHAL.
+     * Registered upon construction of {@link SoundTriggerInstrumentation}
+     * @hide
+     */
+    @TestApi
+    public interface GlobalCallback {
+        /**
+         * Called when the created {@link SoundTriggerInstrumentation} object is invalidated
+         * by another client creating an {@link SoundTriggerInstrumentation} to instrument the
+         * fake STHAL. Only one client may inject at a time.
+         * All sessions are invalidated, no further events will be received, and no
+         * injected events will be delivered.
+         */
+        default void onPreempted() {}
+        /**
+         * Called when the STHAL has been restarted by the framework, due to unexpected
+         * error conditions.
+         * Not called when {@link SoundTriggerInstrumentation#triggerRestart()} is injected.
+         */
+        default void onRestarted() {}
+        /**
+         * Called when the framework detaches from the fake HAL.
+         * This is not transmitted to real HALs, but it indicates that the
+         * framework has flushed its global state.
+         */
+        default void onFrameworkDetached() {}
+        /**
+         * Called when a client application attaches to the framework.
+         * This is not transmitted to real HALs, but it represents the state of
+         * the framework.
+         */
+        default void onClientAttached() {}
+        /**
+         * Called when a client application detaches from the framework.
+         * This is not transmitted to real HALs, but it represents the state of
+         * the framework.
+         */
+        default void onClientDetached() {}
+        /**
+         * Called when the fake HAL receives a model load from the framework.
+         * @param modelSession - A session which exposes additional injection
+         *                       functionality associated with the newly loaded
+         *                       model. See {@link ModelSession}.
+         */
+        void onModelLoaded(@NonNull ModelSession modelSession);
+    }
+
+    /**
+     * Callback for HAL events related to a loaded model. Register with
+     * {@link ModelSession#setModelCallback(Executor, ModelCallback)}
+     * Note, callbacks will not be delivered for events triggered by the injection.
+     * @hide
+     */
+    @TestApi
+    public interface ModelCallback {
+        /**
+         * Called when the model associated with the {@link ModelSession} this callback
+         * was registered for was unloaded by the framework.
+         */
+        default void onModelUnloaded() {}
+        /**
+         * Called when the model associated with the {@link ModelSession} this callback
+         * was registered for receives a set parameter call from the framework.
+         * @param param - Parameter being set.
+         *                 See {@link SoundTrigger.ModelParamTypes}
+         * @param value - Value the model parameter was set to.
+         */
+        default void onParamSet(@SoundTrigger.ModelParamTypes int param, int value) {}
+        /**
+         * Called when the model associated with the {@link ModelSession} this callback
+         * was registered for receives a recognition start request.
+         * @param recognitionSession - A session which exposes additional injection
+         *                             functionality associated with the newly started
+         *                             recognition. See {@link RecognitionSession}
+         */
+        void onRecognitionStarted(@NonNull RecognitionSession recognitionSession);
+    }
+
+    /**
+     * Callback for HAL events related to a started recognition. Register with
+     * {@link RecognitionSession#setRecognitionCallback(Executor, RecognitionCallback)}
+     * Note, callbacks will not be delivered for events triggered by the injection.
+     * @hide
+     */
+    @TestApi
+    public interface RecognitionCallback {
+        /**
+         * Called when the recognition associated with the {@link RecognitionSession} this
+         * callback was registered for was stopped by the framework.
+         */
+        void onRecognitionStopped();
+    }
+
+    /**
+     * Session associated with a loaded model in the fake STHAL.
+     * Can be used to query details about the loaded model, register a callback for future
+     * model events, or trigger HAL events associated with a loaded model.
+     * This session is invalid once the model is unloaded, caused by a
+     * {@link ModelSession#triggerUnloadModel()},
+     * the client unloading recognition, or if a {@link GlobalCallback#onRestarted()} is
+     * received.
+     * Further injections on an invalidated session will not be respected, and no future
+     * callbacks will be delivered.
+     * @hide
+     */
+    @TestApi
+    public class ModelSession {
+
+        /**
+         * Trigger the HAL to preemptively unload the model associated with this session.
+         * Typically occurs when a higher priority model is loaded which utilizes the same
+         * resources.
+         */
+        public void triggerUnloadModel() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                try {
+                    mInjectModelEvent.triggerUnloadModel();
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                mModelSessionMap.remove(mInjectModelEvent.asBinder());
+            }
+        }
+
+        /**
+         * Get the {@link SoundTriggerManager.Model} associated with this session.
+         * @return - The model associated with this session.
+         */
+        public @NonNull SoundTriggerManager.Model getSoundModel() {
+            return mModel;
+        }
+
+        /**
+         * Get the list of {@link SoundTrigger.Keyphrase} associated with this session.
+         * @return - The keyphrases associated with this session.
+         */
+        public @NonNull List<SoundTrigger.Keyphrase> getPhrases() {
+            if (mPhrases == null) {
+                return new ArrayList<>();
+            } else {
+                return new ArrayList<>(Arrays.asList(mPhrases));
+            }
+        }
+
+        /**
+         * Get whether this model is of keyphrase type.
+         * @return - true if the model is a keyphrase model, false otherwise
+         */
+        public boolean isKeyphrase() {
+            return (mPhrases != null);
+        }
+
+        /**
+         * Registers the model callback associated with this session. Events associated
+         * with this model session will be reported via this callback.
+         * See {@link ModelCallback}
+         * @param executor - Executor which the callback is dispatched on
+         * @param callback - Model callback for reporting model session events.
+         */
+        public void setModelCallback(@NonNull @CallbackExecutor Executor executor, @NonNull
+                ModelCallback callback) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mModelCallback = Objects.requireNonNull(callback);
+                mModelExecutor = Objects.requireNonNull(executor);
+            }
+        }
+
+        /**
+         * Clear the model callback associated with this session, if any has been
+         * set by {@link #setModelCallback(Executor, ModelCallback)}.
+         */
+        public void clearModelCallback() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mModelCallback = null;
+                mModelExecutor = null;
+            }
+        }
+
+        private ModelSession(SoundModel model, Phrase[] phrases,
+                IInjectModelEvent injection) {
+            mModel = SoundTriggerManager.Model.create(UUID.fromString(model.uuid),
+                    UUID.fromString(model.vendorUuid),
+                    ConversionUtil.sharedMemoryToByteArray(model.data, model.dataSize));
+            if (phrases != null) {
+                mPhrases = new SoundTrigger.Keyphrase[phrases.length];
+                int i = 0;
+                for (var phrase : phrases) {
+                    mPhrases[i++] = ConversionUtil.aidl2apiPhrase(phrase);
+                }
+            } else {
+                mPhrases = null;
+            }
+            mInjectModelEvent = injection;
+        }
+
+        private void wrap(Consumer<ModelCallback> consumer) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (mModelCallback != null && mModelExecutor != null) {
+                    final ModelCallback callback = mModelCallback;
+                    mModelExecutor.execute(() -> consumer.accept(callback));
+                }
+            }
+        }
+
+        private final SoundTriggerManager.Model mModel;
+        private final SoundTrigger.Keyphrase[] mPhrases;
+        private final IInjectModelEvent mInjectModelEvent;
+
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private ModelCallback mModelCallback = null;
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private Executor mModelExecutor = null;
+    }
+
+    /**
+     * Session associated with a recognition start in the fake STHAL.
+     * Can be used to get information about the started recognition, register a callback
+     * for future events associated with this recognition, and triggering
+     * recognition events or aborts.
+     * This session is invalid once the recognition is stopped, caused by a
+     * {@link RecognitionSession#triggerAbortRecognition()},
+     * {@link RecognitionSession#triggerRecognitionEvent(byte[], List)},
+     * the client stopping recognition, or any operation which invalidates the
+     * {@link ModelSession} which the session was created from.
+     * Further injections on an invalidated session will not be respected, and no future
+     * callbacks will be delivered.
+     * @hide
+     */
+    @TestApi
+    public class RecognitionSession {
+
+        /**
+         * Get an integer token representing the audio session associated with this
+         * recognition in the STHAL.
+         * @return - The session token.
+         */
+        public int getAudioSession() {
+            return mAudioSession;
+        }
+
+        /**
+         * Get the recognition config used to start this recognition.
+         * @return - The config passed to the HAL for startRecognition.
+         */
+        public @NonNull SoundTrigger.RecognitionConfig getRecognitionConfig() {
+            return mRecognitionConfig;
+        }
+
+        /**
+         * Trigger a recognition in the fake STHAL.
+         * @param data - The opaque data buffer included in the recognition event.
+         * @param phraseExtras - Keyphrase metadata included in the event. The
+         *                       event must include metadata for the keyphrase id
+         *                       associated with this model to be received by the
+         *                       client application.
+         */
+        public void triggerRecognitionEvent(@NonNull byte[] data, @Nullable
+                List<SoundTrigger.KeyphraseRecognitionExtra> phraseExtras) {
+            PhraseRecognitionExtra[] converted = null;
+            if (phraseExtras != null) {
+                converted = new PhraseRecognitionExtra[phraseExtras.size()];
+                int i = 0;
+                for (var phraseExtra : phraseExtras) {
+                    converted[i++] = ConversionUtil.api2aidlPhraseRecognitionExtra(phraseExtra);
+                }
+            }
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder());
+                try {
+                    mInjectRecognitionEvent.triggerRecognitionEvent(data, converted);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        /**
+         * Trigger an abort recognition event in the fake HAL. This represents a
+         * preemptive ending of the recognition session by the HAL, despite no
+         * recognition detection. Typically occurs during contention for microphone
+         * usage, or if model limits are hit.
+         * See {@link SoundTriggerInstrumentation#setResourceContention(boolean)} to block
+         * subsequent downward calls for contention reasons.
+         */
+        public void triggerAbortRecognition() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder());
+                try {
+                    mInjectRecognitionEvent.triggerAbortRecognition();
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+         /**
+         * Registers the recognition callback associated with this session. Events associated
+         * with this recognition session will be reported via this callback.
+         * See {@link RecognitionCallback}
+         * @param executor - Executor which the callback is dispatched on
+         * @param callback - Recognition callback for reporting recognition session events.
+         */
+        public void setRecognitionCallback(@NonNull @CallbackExecutor Executor executor,
+                @NonNull RecognitionCallback callback) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionCallback = callback;
+                mRecognitionExecutor = executor;
+            }
+        }
+
+        /**
+         * Clear the recognition callback associated with this session, if any has been
+         * set by {@link #setRecognitionCallback(Executor, RecognitionCallback)}.
+         */
+        public void clearRecognitionCallback() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionCallback = null;
+                mRecognitionExecutor = null;
+            }
+        }
+
+        private RecognitionSession(int audioSession,
+                RecognitionConfig recognitionConfig,
+                IInjectRecognitionEvent injectRecognitionEvent) {
+            mAudioSession = audioSession;
+            mRecognitionConfig = ConversionUtil.aidl2apiRecognitionConfig(recognitionConfig);
+            mInjectRecognitionEvent = injectRecognitionEvent;
+        }
+
+        private void wrap(Consumer<RecognitionCallback> consumer) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (mRecognitionCallback != null && mRecognitionExecutor != null) {
+                    final RecognitionCallback callback = mRecognitionCallback;
+                    mRecognitionExecutor.execute(() -> consumer.accept(callback));
+                }
+            }
+        }
+
+        private final int mAudioSession;
+        private final SoundTrigger.RecognitionConfig mRecognitionConfig;
+        private final IInjectRecognitionEvent mInjectRecognitionEvent;
+
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private Executor mRecognitionExecutor = null;
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private RecognitionCallback mRecognitionCallback = null;
+    }
+
+    // Implementation of injection interface passed to the HAL.
+    // This class will re-associate events received on this callback interface
+    // with sessions, to avoid staleness issues.
+    private class Injection extends ISoundTriggerInjection.Stub {
+        @Override
+        public void registerGlobalEventInjection(IInjectGlobalEvent globalInjection) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mInjectGlobalEvent = globalInjection;
+            }
+        }
+
+        @Override
+        public void onSoundModelLoaded(SoundModel model, @Nullable Phrase[] phrases,
+                            IInjectModelEvent modelInjection, IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                ModelSession modelSession = new ModelSession(model, phrases, modelInjection);
+                mModelSessionMap.put(modelInjection.asBinder(), modelSession);
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onModelLoaded(modelSession));
+            }
+        }
+
+        @Override
+        public void onSoundModelUnloaded(IInjectModelEvent modelSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                ModelSession clientModelSession = mModelSessionMap.remove(modelSession.asBinder());
+                if (clientModelSession == null) return;
+                clientModelSession.wrap((ModelCallback cb) -> cb.onModelUnloaded());
+            }
+        }
+
+        @Override
+        public void onRecognitionStarted(int audioSessionHandle, RecognitionConfig config,
+                IInjectRecognitionEvent recognitionInjection, IInjectModelEvent modelSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder());
+                if (clientModelSession == null) return;
+                RecognitionSession recogSession = new RecognitionSession(
+                        audioSessionHandle, config, recognitionInjection);
+                mRecognitionSessionMap.put(recognitionInjection.asBinder(), recogSession);
+                clientModelSession.wrap((ModelCallback cb) ->
+                        cb.onRecognitionStarted(recogSession));
+            }
+        }
+
+        @Override
+        public void onRecognitionStopped(IInjectRecognitionEvent recognitionSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                RecognitionSession clientRecognitionSession =
+                        mRecognitionSessionMap.remove(recognitionSession.asBinder());
+                if (clientRecognitionSession == null) return;
+                clientRecognitionSession.wrap((RecognitionCallback cb)
+                        -> cb.onRecognitionStopped());
+            }
+        }
+
+        @Override
+        public void onParamSet(int modelParam, int value, IInjectModelEvent modelSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder());
+                if (clientModelSession == null) return;
+                clientModelSession.wrap((ModelCallback cb) -> cb.onParamSet(modelParam, value));
+            }
+        }
+
+
+        @Override
+        public void onRestarted(IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                mRecognitionSessionMap.clear();
+                mModelSessionMap.clear();
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onRestarted());
+            }
+        }
+
+        @Override
+        public void onFrameworkDetached(IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onFrameworkDetached());
+            }
+        }
+
+        @Override
+        public void onClientAttached(IBinder token, IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                mClientToken = token;
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientAttached());
+            }
+        }
+
+        @Override
+        public void onClientDetached(IBinder token) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (token != mClientToken) return;
+                mClientToken = null;
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientDetached());
+            }
+        }
+
+        @Override
+        public void onPreempted() {
+            // This is always valid, independent of session
+            mGlobalCallbackExecutor.execute(() -> mClientCallback.onPreempted());
+            // Callbacks will no longer be delivered, and injection will be silently dropped.
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public SoundTriggerInstrumentation(ISoundTriggerService service,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull GlobalCallback callback) {
+        mClientCallback = Objects.requireNonNull(callback);
+        mGlobalCallbackExecutor = Objects.requireNonNull(executor);
+        try {
+            service.attachInjection(new Injection());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Simulate a HAL restart, typically caused by the framework on an unexpected error,
+     * or a restart of the core audio HAL.
+     * Application sessions will be detached, and all state will be cleared. The framework
+     * will re-attach to the HAL following restart.
+     * @hide
+     */
+    @TestApi
+    public void triggerRestart() {
+        synchronized (mLock) {
+            if (mInjectGlobalEvent == null) {
+                throw new IllegalStateException(
+                        "Attempted to trigger HAL restart before registration");
+            }
+            try {
+                mInjectGlobalEvent.triggerRestart();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Trigger a resource available callback from the fake SoundTrigger HAL to the framework.
+     * This callback notifies the framework that methods which previously failed due to
+     * resource contention may now succeed.
+     * @hide
+     */
+    @TestApi
+    public void triggerOnResourcesAvailable() {
+        synchronized (mLock) {
+            if (mInjectGlobalEvent == null) {
+                throw new IllegalStateException(
+                        "Attempted to trigger HAL resources available before registration");
+            }
+            try {
+                mInjectGlobalEvent.triggerOnResourcesAvailable();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Simulate resource contention, similar to when HAL which does not
+     * support concurrent capture opens a capture stream, or when a HAL
+     * has reached its maximum number of models.
+     * Subsequent model loads and recognition starts will gracefully error.
+     * Since this call does not trigger a callback through the framework, the
+     * call will block until the fake HAL has acknowledged the state change.
+     * @param isResourceContended - true to enable contention, false to return
+     *                              to normal functioning.
+     * @hide
+     */
+    @TestApi
+    public void setResourceContention(boolean isResourceContended) {
+        synchronized (mLock) {
+            if (mInjectGlobalEvent == null) {
+                throw new IllegalStateException("Injection interface not set up");
+            }
+            IInjectGlobalEvent current = mInjectGlobalEvent;
+            final CountDownLatch signal = new CountDownLatch(1);
+            try {
+                current.setResourceContention(isResourceContended, new IAcknowledgeEvent.Stub() {
+                    @Override
+                    public void eventReceived() {
+                        signal.countDown();
+                    }
+                });
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+
+            // Block until we get a callback from the service that our request was serviced.
+            try {
+                // Rely on test timeout if we don't get a response.
+                signal.await();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
+
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index ae8121a..c41bd1b 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -18,11 +18,13 @@
 
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -45,6 +47,7 @@
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.provider.Settings;
 import android.util.Slog;
 
@@ -53,9 +56,9 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.HashMap;
-import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.concurrent.Executor;
 
 /**
  * This class provides management of non-voice (general sound trigger) based sound recognition
@@ -609,4 +612,24 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Create a {@link SoundTriggerInstrumentation} for test purposes, which instruments a fake
+     * STHAL. Clients must attach to the appropriate underlying ST module.
+     * @param executor - Executor to dispatch global callbacks on
+     * @param callback - Callback for unsessioned events received by the fake STHAL
+     * @return - A {@link SoundTriggerInstrumentation} for observation/injection.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    @NonNull
+    public static SoundTriggerInstrumentation attachInstrumentation(
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull SoundTriggerInstrumentation.GlobalCallback callback) {
+        ISoundTriggerService service = ISoundTriggerService.Stub.asInterface(
+                    ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
+        return new SoundTriggerInstrumentation(service, executor, callback);
+    }
+
 }
diff --git a/media/java/android/media/voice/KeyphraseModelManager.java b/media/java/android/media/voice/KeyphraseModelManager.java
index 8ec8967..5a690a5 100644
--- a/media/java/android/media/voice/KeyphraseModelManager.java
+++ b/media/java/android/media/voice/KeyphraseModelManager.java
@@ -21,7 +21,9 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Slog;
@@ -154,4 +156,23 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Override the persistent enrolled model database with an in-memory
+     * fake for testing purposes.
+     *
+     * @param enabled - {@code true} if the model enrollment database should be overridden with an
+     * in-memory fake. {@code false} if the real, persistent model enrollment database should be
+     * used.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+    @TestApi
+    public void setModelDatabaseForTestEnabled(boolean enabled) {
+        try {
+            mVoiceInteractionManagerService.setModelDatabaseForTestEnabled(enabled, new Binder());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 116237f..609c7a4 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -196,6 +196,15 @@
         dstSize = (size_t) env->GetDirectBufferCapacity(byteBuf);
     }
 
+    // unlikely, but GetByteArrayElements() can fail
+    if (dst == nullptr) {
+        ALOGE("no buffer into which to read the data");
+        if (byteArray != NULL) {
+            env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0);
+        }
+        return -ENOMEM;
+    }
+
     if (dstSize < offset) {
         if (byteArray != NULL) {
             env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0);
@@ -204,8 +213,10 @@
         return -ERANGE;
     }
 
+    // passes in the backing memory to use, so it doesn't fail
     sp<ABuffer> buffer = new ABuffer((char *)dst + offset, dstSize - offset);
 
+    buffer->setRange(0, 0);  // mark it empty
     status_t err = mImpl->readSampleData(buffer);
 
     if (byteArray != NULL) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index e53e956..a9bee03 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -107,7 +107,7 @@
 
         initialUiState = when (requestInfo?.type) {
             RequestInfo.TYPE_CREATE -> {
-                val defaultProviderId = userConfigRepo.getDefaultProviderId()
+                val defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId()
                 val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
                 val providerEnableListUiState = getCreateProviderEnableListInitialUiState()
                 val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
@@ -115,12 +115,14 @@
                     getCreateRequestDisplayInfoInitialUiState(originName)!!
                 UiState(
                     createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState(
-                        providerEnableListUiState,
-                        providerDisableListUiState,
-                        defaultProviderId,
-                        requestDisplayInfoUiState,
+                        enabledProviders = providerEnableListUiState,
+                        disabledProviders = providerDisableListUiState,
+                        defaultProviderIdPreferredByApp =
+                        requestDisplayInfoUiState.appPreferredDefaultProviderId,
+                        defaultProviderIdSetByUser = defaultProviderIdSetByUser,
+                        requestDisplayInfo = requestDisplayInfoUiState,
                         isOnPasskeyIntroStateAlready = false,
-                        isPasskeyFirstUse
+                        isPasskeyFirstUse = isPasskeyFirstUse,
                     )!!,
                     getCredentialUiState = null,
                     cancelRequestState = cancelUiRequestState
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 29ec970..4d2bb4c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -250,9 +250,15 @@
             return
         }
         val newUiState = CreateFlowUtils.toCreateCredentialUiState(
-            prevUiState.enabledProviders, prevUiState.disabledProviders,
-            userConfigRepo.getDefaultProviderId(), prevUiState.requestDisplayInfo, true,
-            userConfigRepo.getIsPasskeyFirstUse())
+            enabledProviders = prevUiState.enabledProviders,
+            disabledProviders = prevUiState.disabledProviders,
+            defaultProviderIdPreferredByApp =
+            prevUiState.requestDisplayInfo.appPreferredDefaultProviderId,
+            defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId(),
+            requestDisplayInfo = prevUiState.requestDisplayInfo,
+            isOnPasskeyIntroStateAlready = true,
+            isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
+        )
         if (newUiState == null) {
             Log.d(Constants.LOG_TAG, "Unable to update create ui state")
             onInternalError()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index ca89129..e8d3b1f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -252,13 +252,6 @@
                         ))
                     }
                     is PublicKeyCredentialEntry -> {
-                        val passkeyUsername = credentialEntry.username.toString()
-                        val passkeyDisplayName = credentialEntry.displayName?.toString() ?: ""
-                        val (username, displayName) = userAndDisplayNameForPasskey(
-                            passkeyUsername = passkeyUsername,
-                            passkeyDisplayName = passkeyDisplayName,
-                        )
-
                         result.add(CredentialEntryInfo(
                             providerId = providerId,
                             providerDisplayName = providerLabel,
@@ -268,8 +261,8 @@
                             fillInIntent = it.frameworkExtrasIntent,
                             credentialType = CredentialType.PASSKEY,
                             credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
-                            userName = username,
-                            displayName = displayName,
+                            userName = credentialEntry.username.toString(),
+                            displayName = credentialEntry.displayName?.toString(),
                             icon = credentialEntry.icon.loadDrawable(context),
                             shouldTintIcon = credentialEntry.isDefaultIcon,
                             lastUsedTimeMillis = credentialEntry.lastUsedTime,
@@ -471,6 +464,9 @@
                 createCredentialRequest.candidateQueryData,
                 createCredentialRequest.isSystemProviderRequired
             )
+            val appPreferredDefaultProviderId: String? =
+                if (!requestInfo.hasPermissionToOverrideDefault()) null
+                else createCredentialRequestJetpack?.displayInfo?.preferDefaultProvider
             return when (createCredentialRequestJetpack) {
                 is CreatePasswordRequest -> RequestDisplayInfo(
                     createCredentialRequestJetpack.id,
@@ -479,6 +475,7 @@
                     appLabel,
                     context.getDrawable(R.drawable.ic_password_24) ?: return null,
                     preferImmediatelyAvailableCredentials = false,
+                    appPreferredDefaultProviderId = appPreferredDefaultProviderId,
                 )
                 is CreatePublicKeyCredentialRequest -> {
                     newRequestDisplayInfoFromPasskeyJson(
@@ -487,6 +484,7 @@
                         context = context,
                         preferImmediatelyAvailableCredentials =
                         createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
+                        appPreferredDefaultProviderId = appPreferredDefaultProviderId,
                     )
                 }
                 is CreateCustomCredentialRequest -> {
@@ -502,6 +500,7 @@
                         typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context)
                             ?: context.getDrawable(R.drawable.ic_other_sign_in_24) ?: return null,
                         preferImmediatelyAvailableCredentials = false,
+                        appPreferredDefaultProviderId = appPreferredDefaultProviderId,
                     )
                 }
                 else -> null
@@ -511,20 +510,27 @@
         fun toCreateCredentialUiState(
             enabledProviders: List<EnabledProviderInfo>,
             disabledProviders: List<DisabledProviderInfo>?,
-            defaultProviderId: String?,
+            defaultProviderIdPreferredByApp: String?,
+            defaultProviderIdSetByUser: String?,
             requestDisplayInfo: RequestDisplayInfo,
             isOnPasskeyIntroStateAlready: Boolean,
             isPasskeyFirstUse: Boolean,
         ): CreateCredentialUiState? {
             var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
             var remoteEntry: RemoteInfo? = null
-            var defaultProvider: EnabledProviderInfo? = null
+            var defaultProviderPreferredByApp: EnabledProviderInfo? = null
+            var defaultProviderSetByUser: EnabledProviderInfo? = null
             var createOptionsPairs:
                 MutableList<Pair<CreateOptionInfo, EnabledProviderInfo>> = mutableListOf()
             enabledProviders.forEach { enabledProvider ->
-                if (defaultProviderId != null) {
-                    if (enabledProvider.id == defaultProviderId) {
-                        defaultProvider = enabledProvider
+                if (defaultProviderIdPreferredByApp != null) {
+                    if (enabledProvider.id == defaultProviderIdPreferredByApp) {
+                        defaultProviderPreferredByApp = enabledProvider
+                    }
+                }
+                if (defaultProviderIdSetByUser != null) {
+                    if (enabledProvider.id == defaultProviderIdSetByUser) {
+                        defaultProviderSetByUser = enabledProvider
                     }
                 }
                 if (enabledProvider.createOptions.isNotEmpty()) {
@@ -543,12 +549,14 @@
                     remoteEntry = currRemoteEntry
                 }
             }
+            val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser
             val initialScreenState = toCreateScreenState(
-                /*createOptionSize=*/createOptionsPairs.size,
-                /*isOnPasskeyIntroStateAlready=*/isOnPasskeyIntroStateAlready,
-                /*requestDisplayInfo=*/requestDisplayInfo,
-                /*defaultProvider=*/defaultProvider, /*remoteEntry=*/remoteEntry,
-                /*isPasskeyFirstUse=*/isPasskeyFirstUse
+                createOptionSize = createOptionsPairs.size,
+                isOnPasskeyIntroStateAlready = isOnPasskeyIntroStateAlready,
+                requestDisplayInfo = requestDisplayInfo,
+                defaultProvider = defaultProvider,
+                remoteEntry = remoteEntry,
+                isPasskeyFirstUse = isPasskeyFirstUse
             ) ?: return null
             return CreateCredentialUiState(
                 enabledProviders = enabledProviders,
@@ -674,6 +682,7 @@
             appLabel: String,
             context: Context,
             preferImmediatelyAvailableCredentials: Boolean,
+            appPreferredDefaultProviderId: String?,
         ): RequestDisplayInfo? {
             val json = JSONObject(requestJson)
             var passkeyUsername = ""
@@ -694,6 +703,7 @@
                 appLabel,
                 context.getDrawable(R.drawable.ic_passkey_24) ?: return null,
                 preferImmediatelyAvailableCredentials,
+                appPreferredDefaultProviderId,
             )
         }
     }
@@ -707,7 +717,7 @@
  * 2) username on top if display-name is not available.
  * 3) don't show username on second line if username == display-name
  */
-private fun userAndDisplayNameForPasskey(
+fun userAndDisplayNameForPasskey(
     passkeyUsername: String,
     passkeyDisplayName: String,
 ): Pair<String, String> {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
index 14bf4f2..2df0c7a9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
@@ -27,8 +27,12 @@
 import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 
 @Composable
-fun CredentialListSectionHeader(text: String) {
-    InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurfaceVariant)
+fun CredentialListSectionHeader(text: String, isFirstSection: Boolean) {
+    InternalSectionHeader(
+        text = text,
+        color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+        applyTopPadding = !isFirstSection
+    )
 }
 
 @Composable
@@ -37,8 +41,10 @@
 }
 
 @Composable
-private fun InternalSectionHeader(text: String, color: Color) {
-    Row(modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 8.dp)) {
+private fun InternalSectionHeader(text: String, color: Color, applyTopPadding: Boolean = false) {
+    Row(modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(
+        top = if (applyTopPadding) 8.dp else 0.dp
+    )) {
         SectionHeaderText(
             text,
             modifier = Modifier.padding(top = 20.dp, bottom = 8.dp),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 66d7db8..96010cc 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -435,7 +435,7 @@
     }
     SheetContainerCard {
         item { HeadlineIcon(imageVector = Icons.Outlined.NewReleases) }
-        item { Divider(thickness = 24.dp, color = Color.Transparent) }
+        item { Divider(thickness = 16.dp, color = Color.Transparent) }
         item {
             HeadlineText(
                 text = stringResource(
@@ -633,6 +633,7 @@
             }
         }
         item {
+            Divider(thickness = 8.dp, color = Color.Transparent)
             MoreAboutPasskeySectionHeader(
                 text = stringResource(R.string.public_key_cryptography_title)
             )
@@ -641,6 +642,7 @@
             }
         }
         item {
+            Divider(thickness = 8.dp, color = Color.Transparent)
             MoreAboutPasskeySectionHeader(
                 text = stringResource(R.string.improved_account_security_title)
             )
@@ -649,6 +651,7 @@
             }
         }
         item {
+            Divider(thickness = 8.dp, color = Color.Transparent)
             MoreAboutPasskeySectionHeader(
                 text = stringResource(R.string.seamless_transition_title)
             )
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 4332fb3..12bb629 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -107,6 +107,7 @@
   val appName: String,
   val typeIcon: Drawable,
   val preferImmediatelyAvailableCredentials: Boolean,
+  val appPreferredDefaultProviderId: String?,
 )
 
 /**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index c1ea1d8..98bd020 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -66,6 +66,7 @@
 import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
 import com.android.credentialmanager.common.ui.Snackbar
 import com.android.credentialmanager.logging.GetCredentialEvent
+import com.android.credentialmanager.userAndDisplayNameForPasskey
 import com.android.internal.logging.UiEventLogger.UiEventEnum
 
 @Composable
@@ -322,12 +323,15 @@
             bottomPadding = 0.dp,
         )
     }) {
+        var isFirstSection = true
         // For username
         items(sortedUserNameToCredentialEntryList) { item ->
             PerUserNameCredentials(
                 perUserNameCredentialEntryList = item,
                 onEntrySelected = onEntrySelected,
+                isFirstSection = isFirstSection,
             )
+            isFirstSection = false
         }
         // Locked password manager
         if (authenticationEntryList.isNotEmpty()) {
@@ -335,7 +339,9 @@
                 LockedCredentials(
                     authenticationEntryList = authenticationEntryList,
                     onEntrySelected = onEntrySelected,
+                    isFirstSection = isFirstSection,
                 )
+                isFirstSection = false
             }
         }
         // From another device
@@ -345,15 +351,19 @@
                 RemoteEntryCard(
                     remoteEntry = remoteEntry,
                     onEntrySelected = onEntrySelected,
+                    isFirstSection = isFirstSection,
                 )
+                isFirstSection = false
             }
         }
         // Manage sign-ins (action chips)
         item {
             ActionChips(
                 providerInfoList = providerInfoList,
-                onEntrySelected = onEntrySelected
+                onEntrySelected = onEntrySelected,
+                isFirstSection = isFirstSection,
             )
+            isFirstSection = false
         }
     }
     onLog(GetCredentialEvent.CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD)
@@ -366,6 +376,7 @@
 fun ActionChips(
     providerInfoList: List<ProviderInfo>,
     onEntrySelected: (BaseEntry) -> Unit,
+    isFirstSection: Boolean,
 ) {
     val actionChips = providerInfoList.flatMap { it.actionEntryList }
     if (actionChips.isEmpty()) {
@@ -373,7 +384,8 @@
     }
 
     CredentialListSectionHeader(
-        text = stringResource(R.string.get_dialog_heading_manage_sign_ins)
+        text = stringResource(R.string.get_dialog_heading_manage_sign_ins),
+        isFirstSection = isFirstSection,
     )
     CredentialContainerCard {
         Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
@@ -388,9 +400,11 @@
 fun RemoteEntryCard(
     remoteEntry: RemoteEntryInfo,
     onEntrySelected: (BaseEntry) -> Unit,
+    isFirstSection: Boolean,
 ) {
     CredentialListSectionHeader(
-        text = stringResource(R.string.get_dialog_heading_from_another_device)
+        text = stringResource(R.string.get_dialog_heading_from_another_device),
+        isFirstSection = isFirstSection,
     )
     CredentialContainerCard {
         Column(
@@ -412,9 +426,11 @@
 fun LockedCredentials(
     authenticationEntryList: List<AuthenticationEntryInfo>,
     onEntrySelected: (BaseEntry) -> Unit,
+    isFirstSection: Boolean,
 ) {
     CredentialListSectionHeader(
-        text = stringResource(R.string.get_dialog_heading_locked_password_managers)
+        text = stringResource(R.string.get_dialog_heading_locked_password_managers),
+        isFirstSection = isFirstSection,
     )
     CredentialContainerCard {
         Column(
@@ -432,11 +448,13 @@
 fun PerUserNameCredentials(
     perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
     onEntrySelected: (BaseEntry) -> Unit,
+    isFirstSection: Boolean,
 ) {
     CredentialListSectionHeader(
         text = stringResource(
             R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName
-        )
+        ),
+        isFirstSection = isFirstSection,
     )
     CredentialContainerCard {
         Column(
@@ -457,6 +475,10 @@
     enforceOneLine: Boolean = false,
     onTextLayout: (TextLayoutResult) -> Unit = {},
 ) {
+    val (username, displayName) = if (credentialEntryInfo.credentialType == CredentialType.PASSKEY)
+        userAndDisplayNameForPasskey(
+            credentialEntryInfo.userName, credentialEntryInfo.displayName ?: "")
+    else Pair(credentialEntryInfo.userName, credentialEntryInfo.displayName)
     Entry(
         onClick = { onEntrySelected(credentialEntryInfo) },
         iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(),
@@ -465,13 +487,13 @@
         iconPainter =
         if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24)
         else null,
-        entryHeadlineText = credentialEntryInfo.userName,
+        entryHeadlineText = username,
         entrySecondLineText = if (
             credentialEntryInfo.credentialType == CredentialType.PASSWORD) {
             "••••••••••••"
         } else {
             val itemsToDisplay = listOf(
-                credentialEntryInfo.displayName,
+                displayName,
                 credentialEntryInfo.credentialTypeDisplayName,
                 credentialEntryInfo.providerDisplayName
             ).filterNot(TextUtils::isEmpty)
diff --git a/packages/DynamicSystemInstallationService/AndroidManifest.xml b/packages/DynamicSystemInstallationService/AndroidManifest.xml
index c2aaeac..776bf2b 100644
--- a/packages/DynamicSystemInstallationService/AndroidManifest.xml
+++ b/packages/DynamicSystemInstallationService/AndroidManifest.xml
@@ -36,6 +36,10 @@
                 <data android:scheme="http" />
                 <data android:scheme="https" />
             </intent-filter>
+            <intent-filter>
+                <action android:name="android.os.image.action.START_INSTALL" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
         </activity>
 
         <receiver
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 2c4b478..b265a42 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -19,6 +19,7 @@
 import static android.os.AsyncTask.Status.FINISHED;
 import static android.os.AsyncTask.Status.PENDING;
 import static android.os.AsyncTask.Status.RUNNING;
+import static android.os.image.DynamicSystemClient.ACTION_HIDE_NOTIFICATION;
 import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_IF_IN_USE;
 import static android.os.image.DynamicSystemClient.ACTION_START_INSTALL;
 import static android.os.image.DynamicSystemClient.CAUSE_ERROR_EXCEPTION;
@@ -27,6 +28,8 @@
 import static android.os.image.DynamicSystemClient.CAUSE_INSTALL_CANCELLED;
 import static android.os.image.DynamicSystemClient.CAUSE_INSTALL_COMPLETED;
 import static android.os.image.DynamicSystemClient.CAUSE_NOT_SPECIFIED;
+import static android.os.image.DynamicSystemClient.KEY_ENABLE_WHEN_COMPLETED;
+import static android.os.image.DynamicSystemClient.KEY_ONE_SHOT;
 import static android.os.image.DynamicSystemClient.STATUS_IN_PROGRESS;
 import static android.os.image.DynamicSystemClient.STATUS_IN_USE;
 import static android.os.image.DynamicSystemClient.STATUS_NOT_STARTED;
@@ -77,8 +80,6 @@
 
     private static final String TAG = "DynamicSystemInstallationService";
 
-    // TODO (b/131866826): This is currently for test only. Will move this to System API.
-    static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
     static final String KEY_DSU_SLOT = "KEY_DSU_SLOT";
     static final String DEFAULT_DSU_SLOT = "dsu";
     static final String KEY_PUBKEY = "KEY_PUBKEY";
@@ -172,6 +173,8 @@
 
     // This is for testing only now
     private boolean mEnableWhenCompleted;
+    private boolean mOneShot;
+    private boolean mHideNotification;
 
     private InstallationAsyncTask.Progress mInstallTaskProgress;
     private InstallationAsyncTask mInstallTask;
@@ -229,6 +232,8 @@
             executeRebootToNormalCommand();
         } else if (ACTION_NOTIFY_IF_IN_USE.equals(action)) {
             executeNotifyIfInUseCommand();
+        } else if (ACTION_HIDE_NOTIFICATION.equals(action)) {
+            executeHideNotificationCommand();
         }
 
         return Service.START_NOT_STICKY;
@@ -318,6 +323,7 @@
         long systemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0);
         long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
         mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false);
+        mOneShot = intent.getBooleanExtra(KEY_ONE_SHOT, true);
         String dsuSlot = intent.getStringExtra(KEY_DSU_SLOT);
         String publicKey = intent.getStringExtra(KEY_PUBKEY);
 
@@ -384,9 +390,9 @@
         boolean enabled = false;
 
         if (mInstallTask != null && mInstallTask.isCompleted()) {
-            enabled = mInstallTask.commit();
+            enabled = mInstallTask.commit(mOneShot);
         } else if (isDynamicSystemInstalled()) {
-            enabled = mDynSystem.setEnable(true, true);
+            enabled = mDynSystem.setEnable(true, mOneShot);
         } else {
             Log.e(TAG, "Trying to reboot to AOT while there is no complete installation");
             return;
@@ -439,12 +445,16 @@
     private void executeNotifyIfInUseCommand() {
         switch (getStatus()) {
             case STATUS_IN_USE:
-                startForeground(NOTIFICATION_ID,
-                        buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
+                if (!mHideNotification) {
+                    startForeground(NOTIFICATION_ID,
+                            buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
+                }
                 break;
             case STATUS_READY:
-                startForeground(NOTIFICATION_ID,
-                        buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
+                if (!mHideNotification) {
+                    startForeground(NOTIFICATION_ID,
+                            buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
+                }
                 break;
             case STATUS_IN_PROGRESS:
                 break;
@@ -454,6 +464,16 @@
         }
     }
 
+    private void executeHideNotificationCommand() {
+        mHideNotification = true;
+        switch (getStatus()) {
+            case STATUS_IN_USE:
+            case STATUS_READY:
+                stopForeground(STOP_FOREGROUND_REMOVE);
+                break;
+        }
+    }
+
     private void resetTaskAndStop() {
         resetTaskAndStop(/* removeNotification= */ false);
     }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index a41399f..42b620a 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -803,7 +803,7 @@
         return mIsCompleted;
     }
 
-    boolean commit() {
-        return mDynSystem.setEnable(true, true);
+    boolean commit(boolean oneShot) {
+        return mDynSystem.setEnable(true, oneShot);
     }
 }
diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index 8cda376..bf24c86 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -59,32 +59,36 @@
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
         TextView title = holder.itemView.findViewById(android.R.id.title);
-        if (!TextUtils.isEmpty(mContentDescription)) {
+        if (title != null && !TextUtils.isEmpty(mContentDescription)) {
             title.setContentDescription(mContentDescription);
         }
 
         TextView learnMore = holder.itemView.findViewById(R.id.settingslib_learn_more);
-        if (learnMore != null && mLearnMoreListener != null) {
-            learnMore.setVisibility(View.VISIBLE);
-            if (TextUtils.isEmpty(mLearnMoreText)) {
-                mLearnMoreText = learnMore.getText();
+        if (learnMore != null) {
+            if (mLearnMoreListener != null) {
+                learnMore.setVisibility(View.VISIBLE);
+                if (TextUtils.isEmpty(mLearnMoreText)) {
+                    mLearnMoreText = learnMore.getText();
+                } else {
+                    learnMore.setText(mLearnMoreText);
+                }
+                SpannableString learnMoreText = new SpannableString(mLearnMoreText);
+                if (mLearnMoreSpan != null) {
+                    learnMoreText.removeSpan(mLearnMoreSpan);
+                }
+                mLearnMoreSpan = new FooterLearnMoreSpan(mLearnMoreListener);
+                learnMoreText.setSpan(mLearnMoreSpan, 0,
+                        learnMoreText.length(), 0);
+                learnMore.setText(learnMoreText);
             } else {
-                learnMore.setText(mLearnMoreText);
+                learnMore.setVisibility(View.GONE);
             }
-            SpannableString learnMoreText = new SpannableString(mLearnMoreText);
-            if (mLearnMoreSpan != null) {
-                learnMoreText.removeSpan(mLearnMoreSpan);
-            }
-            mLearnMoreSpan = new FooterLearnMoreSpan(mLearnMoreListener);
-            learnMoreText.setSpan(mLearnMoreSpan, 0,
-                    learnMoreText.length(), 0);
-            learnMore.setText(learnMoreText);
-        } else {
-            learnMore.setVisibility(View.GONE);
         }
 
         View icon = holder.itemView.findViewById(R.id.icon_frame);
-        icon.setVisibility(mIconVisibility);
+        if (icon != null) {
+            icon.setVisibility(mIconVisibility);
+        }
     }
 
     @Override
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 47ac2df..e07a629 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -69,6 +69,7 @@
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
@@ -460,6 +461,9 @@
                 ProvideTextStyle(value = titleTextStyle) {
                     CompositionLocalProvider(
                         LocalContentColor provides titleContentColor,
+                        // Disable the title font scaling by only passing the density but not the
+                        // font scale.
+                        LocalDensity provides Density(density = LocalDensity.current.density),
                         content = title
                     )
                 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
index 30a4349..6f2c38c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
@@ -18,11 +18,11 @@
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Tab
 import androidx.compose.material3.Text
+import androidx.compose.material3.minimumInteractiveComponentSize
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
@@ -46,7 +46,7 @@
         selected = selected,
         onClick = onClick,
         modifier = Modifier
-            .height(48.dp)
+            .minimumInteractiveComponentSize()
             .padding(horizontal = 4.dp, vertical = 6.dp)
             .clip(SettingsShape.CornerMedium)
             .background(
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index 5342def..2c3e58e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -20,9 +20,12 @@
 import android.content.Intent
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.content.pm.ResolveInfo
 import com.android.internal.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
 import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -33,7 +36,11 @@
  */
 internal interface AppListRepository {
     /** Loads the list of [ApplicationInfo]. */
-    suspend fun loadApps(userId: Int, showInstantApps: Boolean): List<ApplicationInfo>
+    suspend fun loadApps(
+        userId: Int,
+        showInstantApps: Boolean = false,
+        matchAnyUserForAdmin: Boolean = false,
+    ): List<ApplicationInfo>
 
     /** Gets the flow of predicate that could used to filter system app. */
     fun showSystemPredicate(
@@ -61,10 +68,12 @@
 
 internal class AppListRepositoryImpl(private val context: Context) : AppListRepository {
     private val packageManager = context.packageManager
+    private val userManager = context.userManager
 
     override suspend fun loadApps(
         userId: Int,
         showInstantApps: Boolean,
+        matchAnyUserForAdmin: Boolean,
     ): List<ApplicationInfo> = coroutineScope {
         val hiddenSystemModulesDeferred = async {
             packageManager.getInstalledModules(0)
@@ -75,12 +84,8 @@
         val hideWhenDisabledPackagesDeferred = async {
             context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
         }
-        val flags = PackageManager.ApplicationInfoFlags.of(
-            (PackageManager.MATCH_DISABLED_COMPONENTS or
-                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
-        )
         val installedApplicationsAsUser =
-            packageManager.getInstalledApplicationsAsUser(flags, userId)
+            getInstalledApplications(userId, matchAnyUserForAdmin)
 
         val hiddenSystemModules = hiddenSystemModulesDeferred.await()
         val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
@@ -89,6 +94,46 @@
         }
     }
 
+    private suspend fun getInstalledApplications(
+        userId: Int,
+        matchAnyUserForAdmin: Boolean,
+    ): List<ApplicationInfo> {
+        val regularFlags = ApplicationInfoFlags.of(
+            (PackageManager.MATCH_DISABLED_COMPONENTS or
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
+        )
+        return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) {
+            packageManager.getInstalledApplicationsAsUser(regularFlags, userId)
+        } else {
+            coroutineScope {
+                val deferredPackageNamesInChildProfiles =
+                    userManager.getProfileIdsWithDisabled(userId)
+                        .filter { it != userId }
+                        .map {
+                            async {
+                                packageManager.getInstalledApplicationsAsUser(regularFlags, it)
+                                    .map { it.packageName }
+                            }
+                        }
+                val adminFlags = ApplicationInfoFlags.of(
+                    PackageManager.MATCH_ANY_USER.toLong() or regularFlags.value
+                )
+                val allInstalledApplications =
+                    packageManager.getInstalledApplicationsAsUser(adminFlags, userId)
+                val packageNamesInChildProfiles = deferredPackageNamesInChildProfiles
+                    .awaitAll()
+                    .flatten()
+                    .toSet()
+                // If an app is for a child profile and not installed on the owner, not display as
+                // 'not installed for this user' in the owner. This will prevent duplicates of work
+                // only apps showing up in the personal profile.
+                allInstalledApplications.filter {
+                    it.installed || it.packageName !in packageNamesInChildProfiles
+                }
+            }
+        }
+    }
+
     override fun showSystemPredicate(
         userIdFlow: Flow<Int>,
         showSystemFlow: Flow<Boolean>,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
index 8896042..bd99ebd 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -80,12 +80,15 @@
     private val scope = viewModelScope + Dispatchers.IO
 
     private val userSubGraphsFlow = appListConfig.flow.map { config ->
-        config.userIds.map { userId -> UserSubGraph(userId, config.showInstantApps) }
+        config.userIds.map { userId ->
+            UserSubGraph(userId, config.showInstantApps, config.matchAnyUserForAdmin)
+        }
     }.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
 
     private inner class UserSubGraph(
         private val userId: Int,
         private val showInstantApps: Boolean,
+        private val matchAnyUserForAdmin: Boolean,
     ) {
         private val userIdFlow = flowOf(userId)
 
@@ -110,7 +113,8 @@
 
         fun reloadApps() {
             scope.launch {
-                appsStateFlow.value = appListRepository.loadApps(userId, showInstantApps)
+                appsStateFlow.value =
+                    appListRepository.loadApps(userId, showInstantApps, matchAnyUserForAdmin)
             }
         }
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index f4a6b59..066db34 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -62,6 +62,7 @@
 data class AppListConfig(
     val userIds: List<Int>,
     val showInstantApps: Boolean,
+    val matchAnyUserForAdmin: Boolean,
 )
 
 data class AppListState(
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 2ebbe8a..89bfa0e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -38,6 +38,7 @@
     title: String,
     listModel: AppListModel<T>,
     showInstantApps: Boolean = false,
+    matchAnyUserForAdmin: Boolean = false,
     primaryUserOnly: Boolean = false,
     noItemMessage: String? = null,
     moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
@@ -59,6 +60,7 @@
                 config = AppListConfig(
                     userIds = userGroup.userInfos.map { it.id },
                     showInstantApps = showInstantApps,
+                    matchAnyUserForAdmin = matchAnyUserForAdmin,
                 ),
                 listModel = listModel,
                 state = AppListState(
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index 57972ed..b732a6a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -23,9 +23,13 @@
 import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.content.pm.PackageManager.ResolveInfoFlags
 import android.content.pm.ResolveInfo
+import android.content.pm.UserInfo
 import android.content.res.Resources
+import android.os.UserManager
+import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.internal.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.first
@@ -35,10 +39,13 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.Spy
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
 import org.mockito.Mockito.`when` as whenever
@@ -49,8 +56,8 @@
     @get:Rule
     val mockito: MockitoRule = MockitoJUnit.rule()
 
-    @Mock
-    private lateinit var context: Context
+    @Spy
+    private val context: Context = ApplicationProvider.getApplicationContext()
 
     @Mock
     private lateinit var resources: Resources
@@ -58,6 +65,9 @@
     @Mock
     private lateinit var packageManager: PackageManager
 
+    @Mock
+    private lateinit var userManager: UserManager
+
     private lateinit var repository: AppListRepository
 
     @Before
@@ -66,36 +76,116 @@
         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
             .thenReturn(emptyArray())
         whenever(context.packageManager).thenReturn(packageManager)
+        whenever(context.userManager).thenReturn(userManager)
         whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList())
         whenever(
-            packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
+            packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), anyInt())
         ).thenReturn(emptyList())
+        whenever(userManager.getUserInfo(ADMIN_USER_ID)).thenReturn(UserInfo().apply {
+            flags = UserInfo.FLAG_ADMIN
+        })
+        whenever(userManager.getProfileIdsWithDisabled(ADMIN_USER_ID))
+            .thenReturn(intArrayOf(ADMIN_USER_ID, MANAGED_PROFILE_USER_ID))
 
         repository = AppListRepositoryImpl(context)
     }
 
-    private fun mockInstalledApplications(apps: List<ApplicationInfo>) {
+    private fun mockInstalledApplications(apps: List<ApplicationInfo>, userId: Int) {
         whenever(
-            packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID))
+            packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(userId))
         ).thenReturn(apps)
     }
 
     @Test
     fun loadApps_notShowInstantApps() = runTest {
-        mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
+        mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID)
 
-        val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+        val appList = repository.loadApps(
+            userId = ADMIN_USER_ID,
+            showInstantApps = false,
+        )
 
-        assertThat(appListFlow).containsExactly(NORMAL_APP)
+        assertThat(appList).containsExactly(NORMAL_APP)
     }
 
     @Test
     fun loadApps_showInstantApps() = runTest {
-        mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
+        mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID)
 
-        val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = true)
+        val appList = repository.loadApps(
+            userId = ADMIN_USER_ID,
+            showInstantApps = true,
+        )
 
-        assertThat(appListFlow).containsExactly(NORMAL_APP, INSTANT_APP)
+        assertThat(appList).containsExactly(NORMAL_APP, INSTANT_APP)
+    }
+
+    @Test
+    fun loadApps_notMatchAnyUserForAdmin_withRegularFlags() = runTest {
+        mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
+
+        val appList = repository.loadApps(
+            userId = ADMIN_USER_ID,
+            matchAnyUserForAdmin = false,
+        )
+
+        assertThat(appList).containsExactly(NORMAL_APP)
+        val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java)
+        verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID))
+        assertThat(flags.value.value).isEqualTo(
+            PackageManager.MATCH_DISABLED_COMPONENTS or
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+        )
+    }
+
+    @Test
+    fun loadApps_matchAnyUserForAdmin_withMatchAnyUserFlag() = runTest {
+        mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
+
+        val appList = repository.loadApps(
+            userId = ADMIN_USER_ID,
+            matchAnyUserForAdmin = true,
+        )
+
+        assertThat(appList).containsExactly(NORMAL_APP)
+        val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java)
+        verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID))
+        assertThat(flags.value.value and PackageManager.MATCH_ANY_USER.toLong()).isGreaterThan(0L)
+    }
+
+    @Test
+    fun loadApps_matchAnyUserForAdminAndInstalledOnManagedProfileOnly_notDisplayed() = runTest {
+        val managedProfileOnlyPackageName = "installed.on.managed.profile.only"
+        mockInstalledApplications(listOf(ApplicationInfo().apply {
+            packageName = managedProfileOnlyPackageName
+        }), ADMIN_USER_ID)
+        mockInstalledApplications(listOf(ApplicationInfo().apply {
+            packageName = managedProfileOnlyPackageName
+            flags = ApplicationInfo.FLAG_INSTALLED
+        }), MANAGED_PROFILE_USER_ID)
+
+        val appList = repository.loadApps(
+            userId = ADMIN_USER_ID,
+            matchAnyUserForAdmin = true,
+        )
+
+        assertThat(appList).isEmpty()
+    }
+
+    @Test
+    fun loadApps_matchAnyUserForAdminAndInstalledOnSecondaryUserOnly_displayed() = runTest {
+        val secondaryUserOnlyApp = ApplicationInfo().apply {
+            packageName = "installed.on.secondary.user.only"
+        }
+        mockInstalledApplications(listOf(secondaryUserOnlyApp), ADMIN_USER_ID)
+        mockInstalledApplications(emptyList(), MANAGED_PROFILE_USER_ID)
+
+        val appList = repository.loadApps(
+            userId = ADMIN_USER_ID,
+            matchAnyUserForAdmin = true,
+        )
+
+        assertThat(appList).containsExactly(secondaryUserOnlyApp)
     }
 
     @Test
@@ -106,11 +196,11 @@
         }
         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
             .thenReturn(arrayOf(app.packageName))
-        mockInstalledApplications(listOf(app))
+        mockInstalledApplications(listOf(app), ADMIN_USER_ID)
 
-        val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
 
-        assertThat(appListFlow).isEmpty()
+        assertThat(appList).isEmpty()
     }
 
     @Test
@@ -122,11 +212,11 @@
         }
         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
             .thenReturn(arrayOf(app.packageName))
-        mockInstalledApplications(listOf(app))
+        mockInstalledApplications(listOf(app), ADMIN_USER_ID)
 
-        val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
 
-        assertThat(appListFlow).isEmpty()
+        assertThat(appList).isEmpty()
     }
 
     @Test
@@ -137,11 +227,11 @@
         }
         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
             .thenReturn(arrayOf(app.packageName))
-        mockInstalledApplications(listOf(app))
+        mockInstalledApplications(listOf(app), ADMIN_USER_ID)
 
-        val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
 
-        assertThat(appListFlow).containsExactly(app)
+        assertThat(appList).containsExactly(app)
     }
 
     @Test
@@ -151,11 +241,11 @@
             enabled = false
             enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
         }
-        mockInstalledApplications(listOf(app))
+        mockInstalledApplications(listOf(app), ADMIN_USER_ID)
 
-        val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
 
-        assertThat(appListFlow).containsExactly(app)
+        assertThat(appList).containsExactly(app)
     }
 
     @Test
@@ -164,11 +254,11 @@
             packageName = "disabled"
             enabled = false
         }
-        mockInstalledApplications(listOf(app))
+        mockInstalledApplications(listOf(app), ADMIN_USER_ID)
 
-        val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
 
-        assertThat(appListFlow).isEmpty()
+        assertThat(appList).isEmpty()
     }
 
     @Test
@@ -219,7 +309,11 @@
         val app = IN_LAUNCHER_APP
 
         whenever(
-            packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
+            packageManager.queryIntentActivitiesAsUser(
+                any(),
+                any<ResolveInfoFlags>(),
+                eq(ADMIN_USER_ID)
+            )
         ).thenReturn(listOf(resolveInfoOf(packageName = app.packageName)))
 
         val showSystemPredicate = getShowSystemPredicate(showSystem = false)
@@ -229,12 +323,16 @@
 
     @Test
     fun getSystemPackageNames_returnExpectedValues() = runTest {
-        mockInstalledApplications(listOf(
-                NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP))
+        mockInstalledApplications(
+            apps = listOf(
+                NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP
+            ),
+            userId = ADMIN_USER_ID,
+        )
 
         val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames(
             context = context,
-            userId = USER_ID,
+            userId = ADMIN_USER_ID,
             showInstantApps = false,
         )
 
@@ -243,12 +341,13 @@
 
     private suspend fun getShowSystemPredicate(showSystem: Boolean) =
         repository.showSystemPredicate(
-            userIdFlow = flowOf(USER_ID),
+            userIdFlow = flowOf(ADMIN_USER_ID),
             showSystemFlow = flowOf(showSystem),
         ).first()
 
     private companion object {
-        const val USER_ID = 0
+        const val ADMIN_USER_ID = 0
+        const val MANAGED_PROFILE_USER_ID = 11
 
         val NORMAL_APP = ApplicationInfo().apply {
             packageName = "normal"
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index 4f0cdde..36d9db5 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -85,7 +85,11 @@
     }
 
     private object FakeAppListRepository : AppListRepository {
-        override suspend fun loadApps(userId: Int, showInstantApps: Boolean) = listOf(APP)
+        override suspend fun loadApps(
+            userId: Int,
+            showInstantApps: Boolean,
+            matchAnyUserForAdmin: Boolean,
+        ) = listOf(APP)
 
         override fun showSystemPredicate(
             userIdFlow: Flow<Int>,
@@ -112,7 +116,11 @@
         const val USER_ID = 0
         const val PACKAGE_NAME = "package.name"
         const val LABEL = "Label"
-        val CONFIG = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false)
+        val CONFIG = AppListConfig(
+            userIds = listOf(USER_ID),
+            showInstantApps = false,
+            matchAnyUserForAdmin = false,
+        )
         val APP = ApplicationInfo().apply {
             packageName = PACKAGE_NAME
         }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index a99d02d..241a134 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -114,7 +114,11 @@
     ) {
         composeTestRule.setContent {
             AppListInput(
-                config = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false),
+                config = AppListConfig(
+                    userIds = listOf(USER_ID),
+                    showInstantApps = false,
+                    matchAnyUserForAdmin = false,
+                ),
                 listModel = TestAppListModel(enableGrouping = enableGrouping),
                 state = AppListState(
                     showSystem = false.toState(),
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index e22f3f0..5fbb4c3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -37,8 +37,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -287,16 +285,7 @@
             return defaultValue;
         }
 
-        try {
-            Method method = mService.getClass().getDeclaredMethod("getDeviceSideInternal",
-                    BluetoothDevice.class);
-            method.setAccessible(true);
-            return (int) method.invoke(mService, device);
-        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
-            Log.e(TAG, "fail to get getDeviceSideInternal\n" + e.toString() + "\n"
-                    + Log.getStackTraceString(new Throwable()));
-            return defaultValue;
-        }
+        return mService.getDeviceSide(device);
     }
 
     /**
@@ -313,17 +302,7 @@
             return defaultValue;
         }
 
-        try {
-            Method method = mService.getClass().getDeclaredMethod("getDeviceModeInternal",
-                    BluetoothDevice.class);
-            method.setAccessible(true);
-            return (int) method.invoke(mService, device);
-        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
-            Log.e(TAG, "fail to get getDeviceModeInternal\n" + e.toString() + "\n"
-                    + Log.getStackTraceString(new Throwable()));
-
-            return defaultValue;
-        }
+        return mService.getDeviceMode(device);
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index a3db6d7..e28ada4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -120,9 +120,9 @@
      * @return true if the request succeeded.
      */
     public static synchronized boolean setPowerSaveMode(Context context,
-            boolean enable, boolean needFirstTimeWarning) {
+            boolean enable, boolean needFirstTimeWarning, @SaverManualEnabledReason int reason) {
         if (DEBUG) {
-            Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF"));
+            Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF") + ", reason: " + reason);
         }
         final ContentResolver cr = context.getContentResolver();
 
@@ -152,6 +152,7 @@
                     sendSystemUiBroadcast(context, ACTION_SHOW_AUTO_SAVER_SUGGESTION,
                             confirmationExtras);
                 }
+                recordBatterySaverEnabledReason(context, reason);
             }
 
             return true;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
index ad022a6..cb386fb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.fuelgauge;
 
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_UNKNOWN;
 import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE;
 import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
 
@@ -72,7 +73,8 @@
         Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isFalse();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+                SAVER_ENABLED_UNKNOWN)).isFalse();
 
         verify(mMockContext, times(1)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(0)).setPowerSaveModeEnabled(anyBoolean());
@@ -92,7 +94,8 @@
         Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -111,7 +114,8 @@
         Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
         Secure.putInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 1);
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -129,7 +133,8 @@
         Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -147,7 +152,8 @@
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
         // When disabling, needFirstTimeWarning doesn't matter.
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
@@ -166,7 +172,8 @@
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
         // When disabling, needFirstTimeWarning doesn't matter.
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index 55125c5..049c90e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -18,6 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -87,4 +90,52 @@
 
         assertThat(mFooterPreference.mIconVisibility).isEqualTo(View.GONE);
     }
+
+    @Test
+    public void onBindViewHolder_whenTitleIsNull_shouldNotRaiseNpe() {
+        PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(R.id.title)).thenReturn(null);
+
+        Throwable actualThrowable = null;
+        try {
+            mFooterPreference.onBindViewHolder(viewHolder);
+        } catch (Throwable throwable) {
+            actualThrowable = throwable;
+        }
+
+        assertThat(actualThrowable).isNull();
+    }
+
+    @Test
+    public void onBindViewHolder_whenLearnMoreIsNull_shouldNotRaiseNpe() {
+        PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(R.id.settingslib_learn_more)).thenReturn(null);
+
+        Throwable actualThrowable = null;
+        try {
+            mFooterPreference.onBindViewHolder(viewHolder);
+        } catch (Throwable throwable) {
+            actualThrowable = throwable;
+        }
+
+        assertThat(actualThrowable).isNull();
+    }
+
+    @Test
+    public void onBindViewHolder_whenIconFrameIsNull_shouldNotRaiseNpe() {
+        PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(R.id.icon_frame)).thenReturn(null);
+
+        Throwable actualThrowable = null;
+        try {
+            mFooterPreference.onBindViewHolder(viewHolder);
+        } catch (Throwable throwable) {
+            actualThrowable = throwable;
+        }
+
+        assertThat(actualThrowable).isNull();
+    }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index fedfb43..78d93bd 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -58,7 +58,6 @@
     <uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
     <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
     <uses-permission android:name="android.permission.BODY_SENSORS" />
-    <uses-permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE" />
     <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
     <uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
     <uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index dabb578..a00f401 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -984,7 +984,13 @@
             android:exported="true"
             android:excludeFromRecents="true"
             android:resizeableActivity="false"
-            android:theme="@android:style/Theme.NoDisplay" />
+            android:theme="@android:style/Theme.NoDisplay" >
+
+            <intent-filter>
+                <action android:name="com.android.systemui.action.LAUNCH_NOTE_TASK"/>
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
 
         <!-- LaunchNoteTaskManagedProfileProxyActivity MUST NOT be exported because it allows caller
              to specify an Android user when launching the default notes app. -->
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 322fc77..05630e7 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -199,6 +199,9 @@
      */
     val hasCustomPositionUpdatedAnimation: Boolean = false,
 
+    /** Transition to AOD should move smartspace like large clock instead of small clock */
+    val useAlternateSmartspaceAODTransition: Boolean = false,
+
     /** True if the clock will react to tone changes in the seed color. */
     val isReactiveToTone: Boolean = true,
 )
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
index 52dfc55..2dd146c5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
@@ -4,7 +4,7 @@
 import androidx.annotation.VisibleForTesting
 
 class WeatherData
-private constructor(
+constructor(
     val description: String,
     val state: WeatherStateIcon,
     val useCelsius: Boolean,
@@ -47,6 +47,7 @@
             }
     }
 
+    // Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon
     enum class WeatherStateIcon(val id: Int) {
         UNKNOWN_ICON(0),
 
diff --git a/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml
new file mode 100644
index 0000000..61a1cb5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml
@@ -0,0 +1,165 @@
+<?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
+  -->
+<animated-vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46">
+            <group android:name="_R_G">
+                <group android:name="_R_G_L_1_G" android:translateX="3.75" android:translateY="8.25">
+                    <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/>
+                    <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/>
+                    <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/>
+                    <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/>
+                </group>
+                <group android:name="_R_G_L_0_G" android:translateX="20.357" android:translateY="35.75" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866">
+                    <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/>
+                </group>
+            </group>
+            <group android:name="time_group"/>
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_3_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
index 951d6fe..f3325ec 100644
--- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
+++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
@@ -104,4 +104,9 @@
         android:fromId="@id/unlocked"
         android:toId="@id/locked"
         android:drawable="@drawable/unlocked_to_locked" />
+
+    <transition
+        android:fromId="@id/locked_fp"
+        android:toId="@id/locked"
+        android:drawable="@drawable/fp_to_locked" />
 </animated-selector>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 2143fc4..edd3047 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -21,19 +21,19 @@
     <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=30] -->
     <string name="keyguard_enter_your_pin">Enter your PIN</string>
 
-    <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=26] -->
+    <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=48] -->
     <string name="keyguard_enter_pin">Enter PIN</string>
 
     <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=30] -->
     <string name="keyguard_enter_your_pattern">Enter your pattern</string>
 
-    <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=26] -->
+    <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=48] -->
     <string name="keyguard_enter_pattern">Draw pattern</string>
 
     <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=30] -->
     <string name="keyguard_enter_your_password">Enter your password</string>
 
-    <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=26] -->
+    <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=48] -->
     <string name="keyguard_enter_password">Enter password</string>
 
     <!-- Shown in the lock screen when there is SIM card IO error. -->
@@ -128,103 +128,103 @@
     <!-- Message shown when user enters wrong pattern -->
     <string name="kg_wrong_pattern">Wrong pattern</string>
 
-    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] -->
     <string name="kg_wrong_pattern_try_again">Wrong pattern. Try again.</string>
 
     <!-- Message shown when user enters wrong password -->
     <string name="kg_wrong_password">Wrong password</string>
 
-    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] -->
     <string name="kg_wrong_password_try_again">Wrong password. Try again.</string>
 
     <!-- Message shown when user enters wrong PIN -->
     <string name="kg_wrong_pin">Wrong PIN</string>
 
-    <!-- Message shown when user enters wrong PIN  [CHAR LIMIT=26] -->
+    <!-- Message shown when user enters wrong PIN  [CHAR LIMIT=48] -->
     <string name="kg_wrong_pin_try_again">Wrong PIN. Try again.</string>
 
-    <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2.  [CHAR LIMIT=52] -->
+    <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2.  [CHAR LIMIT=70] -->
     <string name="kg_wrong_input_try_fp_suggestion">Or unlock with fingerprint</string>
 
-    <!-- Message shown when user fingerprint is not recognized  [CHAR LIMIT=26] -->
+    <!-- Message shown when user fingerprint is not recognized  [CHAR LIMIT=48] -->
     <string name="kg_fp_not_recognized">Fingerprint not recognized</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=26] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=48] -->
     <string name="bouncer_face_not_recognized">Face not recognized</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=52] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=70] -->
     <string name="kg_bio_try_again_or_pin">Try again or enter PIN</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=52] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=70] -->
     <string name="kg_bio_try_again_or_password">Try again or enter password</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=52] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=70] -->
     <string name="kg_bio_try_again_or_pattern">Try again or draw pattern</string>
 
-    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=70] -->
     <string name="kg_bio_too_many_attempts_pin">PIN is required after too many attempts</string>
 
-    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=70] -->
     <string name="kg_bio_too_many_attempts_password">Password is required after too many attempts</string>
 
-    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=70] -->
     <string name="kg_bio_too_many_attempts_pattern">Pattern is required after too many attempts</string>
 
-    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26]  -->
+    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48]  -->
     <string name="kg_unlock_with_pin_or_fp">Unlock with PIN or fingerprint</string>
 
-    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26]  -->
+    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48]  -->
     <string name="kg_unlock_with_password_or_fp">Unlock with password or fingerprint</string>
 
-    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26]  -->
+    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48]  -->
     <string name="kg_unlock_with_pattern_or_fp">Unlock with pattern or fingerprint</string>
 
-    <!-- Message shown when we are on bouncer after Device admin requested lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after Device admin requested lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_dpm_lock">For added security, device was locked by work policy</string>
 
-    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_user_lockdown_pin">PIN is required after lockdown</string>
 
-    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_user_lockdown_password">Password is required after lockdown</string>
 
-    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_user_lockdown_pattern">Pattern is required after lockdown</string>
 
-    <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update.  [CHAR LIMIT=52] -->
+    <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_unattended_update">Update will install during inactive hours</string>
 
-    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=52] -->
+    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_pin_auth_timeout">Added security required. PIN not used for a while.</string>
 
-    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=52] -->
+    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_password_auth_timeout">Added security required. Password not used for a while.</string>
 
-    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=52] -->
+    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=76] -->
     <string name="kg_prompt_pattern_auth_timeout">Added security required. Pattern not used for a while.</string>
 
-    <!-- Message shown when device hasn't been unlocked for a while.  [CHAR LIMIT=52] -->
+    <!-- Message shown when device hasn't been unlocked for a while.  [CHAR LIMIT=82] -->
     <string name="kg_prompt_auth_timeout">Added security required. Device wasn\u2019t unlocked for a while.</string>
 
-    <!-- Message shown when face unlock is not available after too many failed face authentication attempts.  [CHAR LIMIT=52] -->
+    <!-- Message shown when face unlock is not available after too many failed face authentication attempts.  [CHAR LIMIT=70] -->
     <string name="kg_face_locked_out">Can\u2019t unlock with face. Too many attempts.</string>
 
-    <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts.  [CHAR LIMIT=52] -->
+    <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts.  [CHAR LIMIT=75] -->
     <string name="kg_fp_locked_out">Can\u2019t unlock with fingerprint. Too many attempts.</string>
 
-    <!-- Message shown when Trust Agent is disabled.  [CHAR LIMIT=52] -->
+    <!-- Message shown when Trust Agent is disabled.  [CHAR LIMIT=70] -->
     <string name="kg_trust_agent_disabled">Trust agent is unavailable</string>
 
-    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52]  -->
+    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70]  -->
     <string name="kg_primary_auth_locked_out_pin">Too many attempts with incorrect PIN</string>
 
-    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52]  -->
+    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70]  -->
     <string name="kg_primary_auth_locked_out_pattern">Too many attempts with incorrect pattern</string>
 
-    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52]  -->
+    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70]  -->
     <string name="kg_primary_auth_locked_out_password">Too many attempts with incorrect password</string>
 
-    <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=26]-->
+    <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=48]-->
     <string name="kg_too_many_failed_attempts_countdown">{count, plural,
         =1 {Try again in # second.}
         other {Try again in # seconds.}
@@ -296,13 +296,13 @@
     <!-- Description of airplane mode -->
     <string name="airplane_mode">Airplane mode</string>
 
-    <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=52] -->
+    <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=70] -->
     <string name="kg_prompt_reason_restart_pattern">Pattern is required after device restarts</string>
 
-    <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+    <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=70] -->
     <string name="kg_prompt_reason_restart_pin">PIN is required after device restarts</string>
 
-    <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+    <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=70] -->
     <string name="kg_prompt_reason_restart_password">Password is required after device restarts</string>
 
     <!-- An explanation text that the pattern needs to be solved since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
diff --git a/packages/SystemUI/res/drawable/chipbar_background.xml b/packages/SystemUI/res/drawable/chipbar_background.xml
index 5722177..7530f5b 100644
--- a/packages/SystemUI/res/drawable/chipbar_background.xml
+++ b/packages/SystemUI/res/drawable/chipbar_background.xml
@@ -17,6 +17,6 @@
 <shape
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <solid android:color="?androidprv:attr/colorAccentSecondary" />
+    <solid android:color="?androidprv:attr/materialColorSecondaryFixed" />
     <corners android:radius="32dp" />
 </shape>
diff --git a/packages/SystemUI/res/drawable/chipbar_end_button_background.xml b/packages/SystemUI/res/drawable/chipbar_end_button_background.xml
index 80c7207..a3832ee 100644
--- a/packages/SystemUI/res/drawable/chipbar_end_button_background.xml
+++ b/packages/SystemUI/res/drawable/chipbar_end_button_background.xml
@@ -20,7 +20,7 @@
     android:color="?android:textColorPrimary">
     <item android:id="@android:id/background">
         <shape>
-            <solid android:color="@android:color/system_accent1_200"/>
+            <solid android:color="?androidprv:attr/materialColorPrimaryFixedDim"/>
             <corners android:radius="24dp" />
         </shape>
     </item>
diff --git a/packages/SystemUI/res/drawable/hearing.xml b/packages/SystemUI/res/drawable/hearing.xml
new file mode 100644
index 0000000..02f5f92
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M34.4,43.95Q31.55,43.95 29.45,42.4Q27.35,40.85 26.35,38.3Q25.35,35.75 24.375,34.325Q23.4,32.9 20.7,30.75Q17.4,28.1 15.95,25.1Q14.5,22.1 14.5,17.8Q14.5,11.8 18.3,7.975Q22.1,4.15 28.1,4.15Q34,4.15 37.875,7.825Q41.75,11.5 42,17.2H39Q38.75,12.8 35.725,9.975Q32.7,7.15 28.1,7.15Q23.6,7.15 20.55,10.225Q17.5,13.3 17.5,17.8Q17.5,21.4 18.9,24.025Q20.3,26.65 23.55,29.1Q25.5,30.55 26.675,32.25Q27.85,33.95 28.9,36.45Q29.75,38.55 31.125,39.75Q32.5,40.95 34.4,40.95Q36.15,40.95 37.425,39.75Q38.7,38.55 38.95,36.8H41.95Q41.7,39.8 39.55,41.875Q37.4,43.95 34.4,43.95ZM11.95,32.9Q9.1,29.75 7.55,25.825Q6,21.9 6,17.6Q6,13.35 7.475,9.375Q8.95,5.4 11.95,2.35L14.2,4.35Q11.6,7 10.3,10.425Q9,13.85 9,17.6Q9,21.3 10.325,24.725Q11.65,28.15 14.2,30.85ZM28.1,22.45Q26.15,22.45 24.8,21.1Q23.45,19.75 23.45,17.8Q23.45,15.85 24.8,14.45Q26.15,13.05 28.1,13.05Q30.05,13.05 31.45,14.45Q32.85,15.85 32.85,17.8Q32.85,19.75 31.45,21.1Q30.05,22.45 28.1,22.45Z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml
index 762dcdc..8fa975b 100644
--- a/packages/SystemUI/res/layout/chipbar.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -49,15 +49,17 @@
             android:alpha="0.0"
             />
 
+        <!-- LINT.IfChange textColor -->
         <TextView
             android:id="@+id/text"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
-            android:textSize="@dimen/chipbar_text_size"
-            android:textColor="@color/chipbar_text_and_icon_color"
+            style="@style/Chipbar.Text"
+            android:textColor="?androidprv:attr/materialColorOnSecondaryFixed"
             android:alpha="0.0"
             />
+        <!-- LINT.ThenChange(systemui.temporarydisplay.chipbar.ChipbarInfo.kt) -->
 
         <!-- At most one of [loading, failure_icon, undo] will be visible at a time. -->
         <ImageView
@@ -66,7 +68,7 @@
             android:layout_height="@dimen/chipbar_end_icon_size"
             android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
             android:src="@drawable/ic_progress_activity"
-            android:tint="@android:color/system_accent2_700"
+            android:tint="?androidprv:attr/materialColorOnSecondaryFixedVariant"
             android:alpha="0.0"
             />
 
@@ -84,9 +86,9 @@
             android:id="@+id/end_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textColor="?androidprv:attr/textColorOnAccent"
             android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
-            android:textSize="@dimen/chipbar_text_size"
+            style="@style/Chipbar.Text"
+            android:textColor="?androidprv:attr/materialColorOnPrimaryFixed"
             android:paddingStart="@dimen/chipbar_outer_padding"
             android:paddingEnd="@dimen/chipbar_outer_padding"
             android:paddingTop="@dimen/chipbar_end_button_vertical_padding"
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index c0f7029..e9acf3f 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -64,20 +64,20 @@
         android:layout_height="wrap_content"
         android:orientation="horizontal"
         android:layout_gravity="bottom"
+        android:layout_marginHorizontal="@dimen/keyguard_affordance_horizontal_offset"
+        android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+        android:gravity="bottom"
         >
 
         <com.android.systemui.animation.view.LaunchableImageView
             android:id="@+id/start_button"
             android:layout_height="@dimen/keyguard_affordance_fixed_height"
             android:layout_width="@dimen/keyguard_affordance_fixed_width"
-            android:layout_gravity="bottom|start"
             android:scaleType="fitCenter"
             android:padding="@dimen/keyguard_affordance_fixed_padding"
             android:tint="?android:attr/textColorPrimary"
             android:background="@drawable/keyguard_bottom_affordance_bg"
             android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
-            android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
-            android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
             android:visibility="invisible" />
 
         <FrameLayout
@@ -90,7 +90,7 @@
                 android:id="@+id/keyguard_settings_button"
                 layout="@layout/keyguard_settings_popup_menu"
                 android:layout_width="wrap_content"
-                android:layout_height="@dimen/keyguard_affordance_fixed_height"
+                android:layout_height="wrap_content"
                 android:layout_gravity="center"
                 android:visibility="gone"
                 />
@@ -100,14 +100,11 @@
             android:id="@+id/end_button"
             android:layout_height="@dimen/keyguard_affordance_fixed_height"
             android:layout_width="@dimen/keyguard_affordance_fixed_width"
-            android:layout_gravity="bottom|end"
             android:scaleType="fitCenter"
             android:padding="@dimen/keyguard_affordance_fixed_padding"
             android:tint="?android:attr/textColorPrimary"
             android:background="@drawable/keyguard_bottom_affordance_bg"
             android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
-            android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
-            android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
             android:visibility="invisible" />
 
     </LinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
index 9d0d783..65ee8b3 100644
--- a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
+++ b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
@@ -20,7 +20,8 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="wrap_content"
-    android:layout_height="48dp"
+    android:layout_height="wrap_content"
+    android:minHeight="@dimen/keyguard_affordance_fixed_height"
     android:orientation="horizontal"
     android:gravity="center_vertical"
     android:background="@drawable/keyguard_settings_popup_menu_background"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index f1fca76..d710676 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -152,10 +152,4 @@
     <include
         layout="@layout/keyguard_bottom_area"
         android:visibility="gone" />
-
-    <FrameLayout
-        android:id="@+id/preview_container"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-    </FrameLayout>
 </com.android.systemui.shade.NotificationPanelView>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 96e6d4e..cb8c2a7 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -231,9 +231,6 @@
 
     <color name="people_tile_background">@color/material_dynamic_secondary95</color>
 
-    <!-- Chipbar -->
-    <color name="chipbar_text_and_icon_color">@android:color/system_accent2_900</color>
-
     <!-- Internet Dialog -->
     <!-- Material next state on color-->
     <color name="settingslib_state_on_color">@color/settingslib_state_on</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4db42aa..0eb0a07 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1110,6 +1110,7 @@
     <!-- Chipbar -->
     <!-- (Used for media tap-to-transfer chip for sender device and active unlock) -->
     <dimen name="chipbar_outer_padding">16dp</dimen>
+    <dimen name="chipbar_outer_padding_half">8dp</dimen>
     <dimen name="chipbar_text_size">16sp</dimen>
     <dimen name="chipbar_start_icon_size">24dp</dimen>
     <dimen name="chipbar_end_icon_size">20dp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2098aea..a359d3b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -70,6 +70,15 @@
         <item name="android:fontWeight">700</item>
     </style>
 
+    <style name="Chipbar" />
+
+    <style name="Chipbar.Text" parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title">
+        <!-- Text size should be kept in sync with the notification conversation header size. (The
+             conversation header doesn't have a defined style, so the size must be copied here.)
+             See notification_template_conversation_header.xml. -->
+        <item name="android:textSize">16sp</item>
+    </style>
+
     <style name="TextAppearance" />
 
     <style name="TextAppearance.QS">
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index 7971e84..b153785 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -21,7 +21,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
-import android.net.wifi.WifiManager;
+import android.os.Trace;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
@@ -37,6 +37,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository;
 import com.android.systemui.telephony.TelephonyListenerManager;
 
 import java.util.List;
@@ -50,7 +51,10 @@
  * Controller that generates text including the carrier names and/or the status of all the SIM
  * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
  * separated by a given separator {@link CharSequence}.
+ *
+ * @deprecated use {@link com.android.systemui.statusbar.pipeline.wifi} instead
  */
+@Deprecated
 public class CarrierTextManager {
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
     private static final String TAG = "CarrierTextController";
@@ -64,7 +68,7 @@
     private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
     @VisibleForTesting
     protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final WifiManager mWifiManager;
+    private final WifiRepository mWifiRepository;
     private final boolean[] mSimErrorState;
     private final int mSimSlotsNumber;
     @Nullable // Check for nullability before dispatching
@@ -165,7 +169,7 @@
             CharSequence separator,
             boolean showAirplaneMode,
             boolean showMissingSim,
-            @Nullable WifiManager wifiManager,
+            WifiRepository wifiRepository,
             TelephonyManager telephonyManager,
             TelephonyListenerManager telephonyListenerManager,
             WakefulnessLifecycle wakefulnessLifecycle,
@@ -177,8 +181,7 @@
 
         mShowAirplaneMode = showAirplaneMode;
         mShowMissingSim = showMissingSim;
-
-        mWifiManager = wifiManager;
+        mWifiRepository = wifiRepository;
         mTelephonyManager = telephonyManager;
         mSeparator = separator;
         mTelephonyListenerManager = telephonyListenerManager;
@@ -297,6 +300,7 @@
     }
 
     protected void updateCarrierText() {
+        Trace.beginSection("CarrierTextManager#updateCarrierText");
         boolean allSimsMissing = true;
         boolean anySimReadyAndInService = false;
         CharSequence displayText = null;
@@ -329,20 +333,20 @@
                 carrierNames[i] = carrierTextForSimState;
             }
             if (simState == TelephonyManager.SIM_STATE_READY) {
+                Trace.beginSection("WFC check");
                 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
                 if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
                     // hack for WFC (IWLAN) not turning off immediately once
                     // Wi-Fi is disassociated or disabled
                     if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
-                            || (mWifiManager != null && mWifiManager.isWifiEnabled()
-                            && mWifiManager.getConnectionInfo() != null
-                            && mWifiManager.getConnectionInfo().getBSSID() != null)) {
+                            || mWifiRepository.isWifiConnectedWithValidSsid()) {
                         if (DEBUG) {
                             Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
                         }
                         anySimReadyAndInService = true;
                     }
                 }
+                Trace.endSection();
             }
         }
         // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
@@ -406,6 +410,7 @@
                 subsIds,
                 airplaneMode);
         postToCallback(info);
+        Trace.endSection();
     }
 
     @VisibleForTesting
@@ -633,7 +638,7 @@
     public static class Builder {
         private final Context mContext;
         private final String mSeparator;
-        private final WifiManager mWifiManager;
+        private final WifiRepository mWifiRepository;
         private final TelephonyManager mTelephonyManager;
         private final TelephonyListenerManager mTelephonyListenerManager;
         private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -647,7 +652,7 @@
         public Builder(
                 Context context,
                 @Main Resources resources,
-                @Nullable WifiManager wifiManager,
+                @Nullable WifiRepository wifiRepository,
                 TelephonyManager telephonyManager,
                 TelephonyListenerManager telephonyListenerManager,
                 WakefulnessLifecycle wakefulnessLifecycle,
@@ -657,7 +662,7 @@
             mContext = context;
             mSeparator = resources.getString(
                     com.android.internal.R.string.kg_text_message_separator);
-            mWifiManager = wifiManager;
+            mWifiRepository = wifiRepository;
             mTelephonyManager = telephonyManager;
             mTelephonyListenerManager = telephonyListenerManager;
             mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -681,7 +686,7 @@
         /** Create a CarrierTextManager. */
         public CarrierTextManager build() {
             return new CarrierTextManager(
-                    mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiManager,
+                    mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository,
                     mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle,
                     mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 0779653..7262a73 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -25,6 +25,7 @@
 import android.text.format.DateFormat
 import android.util.TypedValue
 import android.view.View
+import android.view.View.OnAttachStateChangeListener
 import android.view.ViewTreeObserver
 import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
@@ -105,6 +106,24 @@
                 }
                 updateFontSizes()
                 updateTimeListeners()
+                value.smallClock.view.addOnAttachStateChangeListener(
+                    object : OnAttachStateChangeListener {
+                        override fun onViewAttachedToWindow(p0: View?) {
+                            value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+                        }
+
+                        override fun onViewDetachedFromWindow(p0: View?) {
+                        }
+                })
+                value.largeClock.view.addOnAttachStateChangeListener(
+                    object : OnAttachStateChangeListener {
+                        override fun onViewAttachedToWindow(p0: View?) {
+                            value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+                        }
+
+                        override fun onViewDetachedFromWindow(p0: View?) {
+                        }
+                })
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 9290220..25d1792 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -109,7 +109,7 @@
     private final ContentObserver mShowWeatherObserver = new ContentObserver(null) {
         @Override
         public void onChange(boolean change) {
-            setDateWeatherVisibility();
+            setWeatherVisibility();
         }
     };
 
@@ -236,6 +236,7 @@
 
         updateDoubleLineClock();
         setDateWeatherVisibility();
+        setWeatherVisibility();
 
         mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
                 mKeyguardUnlockAnimationListener);
@@ -266,6 +267,8 @@
                     mStatusArea.removeView(mDateWeatherView);
                     addDateWeatherView(index);
                 }
+                setDateWeatherVisibility();
+                setWeatherVisibility();
             }
             int index = mStatusArea.indexOfChild(mSmartspaceView);
             if (index >= 0) {
@@ -487,16 +490,19 @@
     }
 
     private void setDateWeatherVisibility() {
-        if (mDateWeatherView != null || mWeatherView != null) {
+        if (mDateWeatherView != null) {
             mUiExecutor.execute(() -> {
-                if (mDateWeatherView != null) {
-                    mDateWeatherView.setVisibility(
-                            clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE);
-                }
-                if (mWeatherView != null) {
-                    mWeatherView.setVisibility(
-                            mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE);
-                }
+                mDateWeatherView.setVisibility(
+                        clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE);
+            });
+        }
+    }
+
+    private void setWeatherVisibility() {
+        if (mWeatherView != null) {
+            mUiExecutor.execute(() -> {
+                mWeatherView.setVisibility(
+                        mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE);
             });
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 87a7758..25cda98 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -75,6 +75,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -115,6 +116,7 @@
     private final SessionTracker mSessionTracker;
     private final Optional<SideFpsController> mSideFpsController;
     private final FalsingA11yDelegate mFalsingA11yDelegate;
+    private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     private int mTranslationY;
     // Whether the volume keys should be handled by keyguard. If true, then
     // they will be handled here for specific media types such as music, otherwise
@@ -300,6 +302,7 @@
         @Override
         public void onSwipeUp() {
             if (!mUpdateMonitor.isFaceDetectionRunning()) {
+                mKeyguardFaceAuthInteractor.onSwipeUpOnBouncer();
                 boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
                         FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
                 mKeyguardSecurityCallback.userActivity();
@@ -389,7 +392,8 @@
             FalsingA11yDelegate falsingA11yDelegate,
             TelephonyManager telephonyManager,
             ViewMediatorCallback viewMediatorCallback,
-            AudioManager audioManager
+            AudioManager audioManager,
+            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor
     ) {
         super(view);
         mLockPatternUtils = lockPatternUtils;
@@ -414,6 +418,7 @@
         mTelephonyManager = telephonyManager;
         mViewMediatorCallback = viewMediatorCallback;
         mAudioManager = audioManager;
+        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
     }
 
     @Override
@@ -526,7 +531,6 @@
     public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) {
         if (mCancelAction != null) {
             mCancelAction.run();
-            mCancelAction = null;
         }
         mDismissAction = action;
         mCancelAction = cancelAction;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index b8e196f..d8e1eb0f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -19,10 +19,12 @@
 import static java.util.Collections.emptySet;
 
 import android.content.Context;
+import android.os.Build;
 import android.os.Trace;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
 import android.widget.GridLayout;
 
 import com.android.systemui.R;
@@ -118,6 +120,16 @@
     }
 
     @Override
+    public ViewPropertyAnimator animate() {
+        if (Build.IS_DEBUGGABLE) {
+            throw new IllegalArgumentException(
+                    "KeyguardStatusView does not support ViewPropertyAnimator. "
+                            + "Use PropertyAnimator instead.");
+        }
+        return super.animate();
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         Trace.beginSection("KeyguardStatusView#onMeasure");
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 6f54988..0cdef4d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -73,7 +73,7 @@
      */
     private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
 
-    private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES =
+    public static final AnimationProperties CLOCK_ANIMATION_PROPERTIES =
             new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
 
     private final KeyguardSliceViewController mKeyguardSliceViewController;
@@ -221,16 +221,32 @@
         mView.setImportantForAccessibility(mode);
     }
 
+    @VisibleForTesting
+    void setProperty(AnimatableProperty property, float value, boolean animate) {
+        PropertyAnimator.setProperty(mView, property, value, CLOCK_ANIMATION_PROPERTIES, animate);
+    }
+
     /**
      * Update position of the view with an optional animation
      */
     public void updatePosition(int x, int y, float scale, boolean animate) {
         float oldY = mView.getY();
-        PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES,
-                animate);
+        setProperty(AnimatableProperty.Y, y, animate);
 
-        mKeyguardClockSwitchController.updatePosition(x, scale, CLOCK_ANIMATION_PROPERTIES,
-                animate);
+        ClockController clock = mKeyguardClockSwitchController.getClock();
+        if (clock != null && clock.getConfig().getUseAlternateSmartspaceAODTransition()) {
+            // If requested, scale the entire view instead of just the clock view
+            mKeyguardClockSwitchController.updatePosition(x, 1f /* scale */,
+                    CLOCK_ANIMATION_PROPERTIES, animate);
+            setProperty(AnimatableProperty.SCALE_X, scale, animate);
+            setProperty(AnimatableProperty.SCALE_Y, scale, animate);
+        } else {
+            mKeyguardClockSwitchController.updatePosition(x, scale,
+                    CLOCK_ANIMATION_PROPERTIES, animate);
+            setProperty(AnimatableProperty.SCALE_X, 1f, animate);
+            setProperty(AnimatableProperty.SCALE_Y, 1f, animate);
+        }
+
         if (oldY != y) {
             mKeyguardClockSwitchController.updateKeyguardStatusViewOffset();
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 350c4ed..33a8224 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -154,6 +154,15 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.DumpsysTableLogger;
+import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.DetectionStatus;
+import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus;
 import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.WeatherData;
@@ -372,6 +381,8 @@
     private final FingerprintManager mFpm;
     @Nullable
     private final FaceManager mFaceManager;
+    @Nullable
+    private KeyguardFaceAuthInteractor mFaceAuthInteractor;
     private final LockPatternUtils mLockPatternUtils;
     @VisibleForTesting
     @DevicePostureInt
@@ -1165,8 +1176,21 @@
         Trace.endSection();
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceAuthFailed() {
         Assert.isMainThread();
+        String reason =
+                mKeyguardBypassController.canBypass() ? "bypass"
+                        : mAlternateBouncerShowing ? "alternateBouncer"
+                                : mPrimaryBouncerFullyShown ? "bouncer"
+                                        : "udfpsFpDown";
+        requestActiveUnlock(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+                "faceFailure-" + reason);
+
         mLogger.d("onFaceAuthFailed");
         mFaceCancelSignal = null;
         setFaceRunningState(BIOMETRIC_STATE_STOPPED);
@@ -1180,6 +1204,10 @@
                 mContext.getString(R.string.kg_face_not_recognized));
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceAcquired(int acquireInfo) {
         Assert.isMainThread();
         mLogger.logFaceAcquired(acquireInfo);
@@ -1189,8 +1217,19 @@
                 cb.onBiometricAcquired(FACE, acquireInfo);
             }
         }
+
+        if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                acquireInfo)) {
+            requestActiveUnlock(
+                    ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+                    "faceAcquireInfo-" + acquireInfo);
+        }
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
         try {
@@ -1203,7 +1242,7 @@
                 mLogger.logFaceAuthForWrongUser(authUserId);
                 return;
             }
-            if (isFaceDisabled(userId)) {
+            if (!isFaceAuthInteractorEnabled() && isFaceDisabled(userId)) {
                 mLogger.logFaceAuthDisabledForUser(userId);
                 return;
             }
@@ -1215,6 +1254,10 @@
         Trace.endSection();
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceHelp(int msgId, String helpString) {
         Assert.isMainThread();
         mLogger.logFaceAuthHelpMsg(msgId, helpString);
@@ -1226,22 +1269,10 @@
         }
     }
 
-    private final Runnable mRetryFaceAuthentication = new Runnable() {
-        @Override
-        public void run() {
-            mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
-            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
-                    FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE);
-        }
-    };
-
-    private void onFaceCancelNotReceived() {
-        mLogger.e("Face cancellation not received, transitioning to STOPPED");
-        mFaceRunningState = BIOMETRIC_STATE_STOPPED;
-        KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP,
-                FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED);
-    }
-
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceError(int msgId, final String originalErrMsg) {
         Assert.isMainThread();
         String errString = originalErrMsg;
@@ -1299,6 +1330,28 @@
         if (lockedOutStateChanged) {
             notifyLockedOutStateChanged(FACE);
         }
+
+        if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(msgId)) {
+            requestActiveUnlock(
+                    ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+                    "faceError-" + msgId);
+        }
+    }
+
+    private final Runnable mRetryFaceAuthentication = new Runnable() {
+        @Override
+        public void run() {
+            mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
+            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+                    FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE);
+        }
+    };
+
+    private void onFaceCancelNotReceived() {
+        mLogger.e("Face cancellation not received, transitioning to STOPPED");
+        mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+        KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP,
+                FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED);
     }
 
     private void handleFaceLockoutReset(@LockoutMode int mode) {
@@ -1343,10 +1396,61 @@
         return mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture.
+     */
+    @Deprecated
     public boolean isFaceDetectionRunning() {
+        if (isFaceAuthInteractorEnabled()) {
+            return getFaceAuthInteractor().isRunning();
+        }
         return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
     }
 
+    private boolean isFaceAuthInteractorEnabled() {
+        return mFaceAuthInteractor != null && mFaceAuthInteractor.isEnabled();
+    }
+
+    private @Nullable KeyguardFaceAuthInteractor getFaceAuthInteractor() {
+        return mFaceAuthInteractor;
+    }
+
+    /**
+     * Set the face auth interactor that should be used for initiating face authentication.
+     */
+    public void setFaceAuthInteractor(@Nullable KeyguardFaceAuthInteractor faceAuthInteractor) {
+        mFaceAuthInteractor = faceAuthInteractor;
+        mFaceAuthInteractor.registerListener(mFaceAuthenticationListener);
+    }
+
+    private FaceAuthenticationListener mFaceAuthenticationListener =
+            new FaceAuthenticationListener() {
+                @Override
+                public void onAuthenticationStatusChanged(@NonNull AuthenticationStatus status) {
+                    if (status instanceof AcquiredAuthenticationStatus) {
+                        handleFaceAcquired(
+                                ((AcquiredAuthenticationStatus) status).getAcquiredInfo());
+                    } else if (status instanceof ErrorAuthenticationStatus) {
+                        ErrorAuthenticationStatus error = (ErrorAuthenticationStatus) status;
+                        handleFaceError(error.getMsgId(), error.getMsg());
+                    } else if (status instanceof FailedAuthenticationStatus) {
+                        handleFaceAuthFailed();
+                    } else if (status instanceof HelpAuthenticationStatus) {
+                        HelpAuthenticationStatus helpMsg = (HelpAuthenticationStatus) status;
+                        handleFaceHelp(helpMsg.getMsgId(), helpMsg.getMsg());
+                    } else if (status instanceof SuccessAuthenticationStatus) {
+                        FaceManager.AuthenticationResult result =
+                                ((SuccessAuthenticationStatus) status).getSuccessResult();
+                        handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
+                    }
+                }
+
+                @Override
+                public void onDetectionStatusChanged(@NonNull DetectionStatus status) {
+                    handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric());
+                }
+            };
+
     private boolean isTrustDisabled() {
         // Don't allow trust agent if device is secured with a SIM PIN. This is here
         // mainly because there's no other way to prompt the user to enter their SIM PIN
@@ -1360,6 +1464,10 @@
                 || isSimPinSecure();
     }
 
+    /**
+     * @deprecated This method is not needed anymore with the new face auth system.
+     */
+    @Deprecated
     private boolean isFaceDisabled(int userId) {
         // TODO(b/140035044)
         return whitelistIpcs(() ->
@@ -1371,7 +1479,10 @@
     /**
      * @return whether the current user has been authenticated with face. This may be true
      * on the lockscreen if the user doesn't have bypass enabled.
+     *
+     * @deprecated This is being migrated to use modern architecture.
      */
+    @Deprecated
     public boolean getIsFaceAuthenticated() {
         boolean faceAuthenticated = false;
         BiometricAuthenticated bioFaceAuthenticated = mUserFaceAuthenticated.get(getCurrentUser());
@@ -1619,6 +1730,9 @@
     void setAssistantVisible(boolean assistantVisible) {
         mAssistantVisible = assistantVisible;
         mLogger.logAssistantVisible(mAssistantVisible);
+        if (isFaceAuthInteractorEnabled()) {
+            mFaceAuthInteractor.onAssistantTriggeredOnLockScreen();
+        }
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
                 FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED);
         if (mAssistantVisible) {
@@ -1832,54 +1946,27 @@
 
                 @Override
                 public void onAuthenticationFailed() {
-                        String reason =
-                                mKeyguardBypassController.canBypass() ? "bypass"
-                                        : mAlternateBouncerShowing ? "alternateBouncer"
-                                                : mPrimaryBouncerFullyShown ? "bouncer"
-                                                        : "udfpsFpDown";
-                        requestActiveUnlock(
-                                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
-                                "faceFailure-" + reason);
-
                     handleFaceAuthFailed();
                 }
 
                 @Override
                 public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {
-                    Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
                     handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
-                    Trace.endSection();
                 }
 
                 @Override
                 public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
-                    if (mFaceAcquiredInfoIgnoreList.contains(helpMsgId)) {
-                        return;
-                    }
                     handleFaceHelp(helpMsgId, helpString.toString());
                 }
 
                 @Override
                 public void onAuthenticationError(int errMsgId, CharSequence errString) {
                     handleFaceError(errMsgId, errString.toString());
-
-                    if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(errMsgId)) {
-                        requestActiveUnlock(
-                                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
-                                "faceError-" + errMsgId);
-                    }
                 }
 
                 @Override
                 public void onAuthenticationAcquired(int acquireInfo) {
                     handleFaceAcquired(acquireInfo);
-
-                    if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
-                            acquireInfo)) {
-                        requestActiveUnlock(
-                                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
-                                "faceAcquireInfo-" + acquireInfo);
-                    }
                 }
     };
 
@@ -2628,7 +2715,9 @@
      * @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being
      * invoked.
      * @return current face auth detection state, true if it is running.
+     * @deprecated This is being migrated to use modern architecture.
      */
+    @Deprecated
     public boolean requestFaceAuth(@FaceAuthApiRequestReason String reason) {
         mLogger.logFaceAuthRequested(reason);
         updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
@@ -2643,6 +2732,7 @@
     }
 
     private void updateFaceListeningState(int action, @NonNull FaceAuthUiEvent faceAuthUiEvent) {
+        if (isFaceAuthInteractorEnabled()) return;
         // If this message exists, we should not authenticate again until this message is
         // consumed by the handler
         if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
@@ -3154,6 +3244,9 @@
     }
 
     public boolean isFaceLockedOut() {
+        if (isFaceAuthInteractorEnabled()) {
+            return getFaceAuthInteractor().isLockedOut();
+        }
         return mFaceLockedOutPermanent;
     }
 
@@ -3202,13 +3295,23 @@
         return mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture.
+     */
+    @Deprecated
     private boolean isUnlockWithFacePossible(int userId) {
+        if (isFaceAuthInteractorEnabled()) {
+            return getFaceAuthInteractor().canFaceAuthRun();
+        }
         return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId);
     }
 
     /**
      * If face hardware is available, user has enrolled and enabled auth via setting.
+     *
+     * @deprecated This is being migrated to use modern architecture.
      */
+    @Deprecated
     public boolean isFaceAuthEnabledForUser(int userId) {
         // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
         updateFaceEnrolled(userId);
@@ -3232,6 +3335,7 @@
     }
 
     private void stopListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) {
+        if (isFaceAuthInteractorEnabled()) return;
         mLogger.v("stopListeningForFace()");
         mLogger.logStoppedListeningForFace(mFaceRunningState, faceAuthUiEvent.getReason());
         mUiEventLogger.log(faceAuthUiEvent, getKeyguardSessionId());
@@ -4098,6 +4202,9 @@
         mStatusBarStateController.removeCallback(mStatusBarStateControllerListener);
         mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
         mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
+        if (isFaceAuthInteractorEnabled()) {
+            mFaceAuthInteractor.unregisterListener(mFaceAuthenticationListener);
+        }
 
         if (mDeviceProvisionedObserver != null) {
             mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);
@@ -4127,6 +4234,7 @@
         pw.println("  getUserHasTrust()=" + getUserHasTrust(getCurrentUser()));
         pw.println("  getUserUnlockedWithBiometric()="
                 + getUserUnlockedWithBiometric(getCurrentUser()));
+        pw.println("  isFaceAuthInteractorEnabled: " + isFaceAuthInteractorEnabled());
         pw.println("  SIM States:");
         for (SimData data : mSimDatas.values()) {
             pw.println("    " + data.toString());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index a678edc..651c979 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -18,8 +18,8 @@
 
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 
+import android.util.Property;
 import android.view.View;
-import android.view.ViewPropertyAnimator;
 
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.log.LogBuffer;
@@ -34,6 +34,8 @@
 
 import com.google.errorprone.annotations.CompileTimeConstant;
 
+import java.util.function.Consumer;
+
 /**
  * Helper class for updating visibility of keyguard views based on keyguard and status bar state.
  * This logic is shared by both the keyguard status view and the keyguard user switcher.
@@ -83,47 +85,49 @@
             boolean keyguardFadingAway,
             boolean goingToFullShade,
             int oldStatusBarState) {
-        mView.animate().cancel();
+        PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA);
         boolean isOccluded = mKeyguardStateController.isOccluded();
         mKeyguardViewVisibilityAnimating = false;
 
         if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
                 && statusBarState != KEYGUARD) || goingToFullShade) {
             mKeyguardViewVisibilityAnimating = true;
-            mView.animate()
-                    .alpha(0f)
-                    .setStartDelay(0)
-                    .setDuration(160)
-                    .setInterpolator(Interpolators.ALPHA_OUT)
-                    .withEndAction(
-                            mAnimateKeyguardStatusViewGoneEndRunnable);
+
+            AnimationProperties animProps = new AnimationProperties()
+                    .setCustomInterpolator(View.ALPHA, Interpolators.ALPHA_OUT)
+                    .setAnimationEndAction(mSetGoneEndAction);
             if (keyguardFadingAway) {
-                mView.animate()
-                        .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
-                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
-                        .start();
+                animProps
+                        .setDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration());
                 log("goingToFullShade && keyguardFadingAway");
             } else {
+                animProps.setDelay(0).setDuration(160);
                 log("goingToFullShade && !keyguardFadingAway");
             }
+            PropertyAnimator.setProperty(
+                    mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
         } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
             mView.setVisibility(View.VISIBLE);
             mKeyguardViewVisibilityAnimating = true;
             mView.setAlpha(0f);
-            mView.animate()
-                    .alpha(1f)
-                    .setStartDelay(0)
-                    .setDuration(320)
-                    .setInterpolator(Interpolators.ALPHA_IN)
-                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
+            PropertyAnimator.setProperty(
+                    mView, AnimatableProperty.ALPHA, 1f,
+                    new AnimationProperties()
+                            .setDelay(0)
+                            .setDuration(320)
+                            .setCustomInterpolator(View.ALPHA, Interpolators.ALPHA_IN)
+                            .setAnimationEndAction(
+                                    property -> mSetVisibleEndRunnable.run()),
+                    true /* animate */);
             log("keyguardFadingAway transition w/ Y Aniamtion");
         } else if (statusBarState == KEYGUARD) {
             if (keyguardFadingAway) {
                 mKeyguardViewVisibilityAnimating = true;
-                ViewPropertyAnimator animator = mView.animate()
-                        .alpha(0)
-                        .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
-                        .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable);
+                AnimationProperties animProps = new AnimationProperties()
+                        .setDelay(0)
+                        .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_LINEAR_IN)
+                        .setAnimationEndAction(mSetInvisibleEndAction);
                 if (mAnimateYPos) {
                     float target = mView.getY() - mView.getHeight() * 0.05f;
                     int delay = 0;
@@ -135,13 +139,16 @@
                     PropertyAnimator.setProperty(mView, AnimatableProperty.Y, target,
                             mAnimationProperties,
                             true /* animate */);
-                    animator.setDuration(duration)
-                            .setStartDelay(delay);
+                    animProps.setDuration(duration)
+                            .setDelay(delay);
                     log("keyguardFadingAway transition w/ Y Aniamtion");
                 } else {
                     log("keyguardFadingAway transition w/o Y Animation");
                 }
-                animator.start();
+                PropertyAnimator.setProperty(
+                        mView, AnimatableProperty.ALPHA, 0f,
+                        animProps,
+                        true /* animate */);
             } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
                 log("ScreenOff transition");
                 mKeyguardViewVisibilityAnimating = true;
@@ -149,7 +156,7 @@
                 // Ask the screen off animation controller to animate the keyguard visibility for us
                 // since it may need to be cancelled due to keyguard lifecycle events.
                 mScreenOffAnimationController.animateInKeyguard(
-                        mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
+                        mView, mSetVisibleEndRunnable);
             } else {
                 log("Direct set Visibility to VISIBLE");
                 mView.setVisibility(View.VISIBLE);
@@ -163,19 +170,25 @@
         mLastOccludedState = isOccluded;
     }
 
-    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
-        mKeyguardViewVisibilityAnimating = false;
-        mView.setVisibility(View.INVISIBLE);
-        log("Callback Set Visibility to INVISIBLE");
+    private final Consumer<Property> mSetInvisibleEndAction = new Consumer<>() {
+        @Override
+        public void accept(Property property) {
+            mKeyguardViewVisibilityAnimating = false;
+            mView.setVisibility(View.INVISIBLE);
+            log("Callback Set Visibility to INVISIBLE");
+        }
     };
 
-    private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
-        mKeyguardViewVisibilityAnimating = false;
-        mView.setVisibility(View.GONE);
-        log("CallbackSet Visibility to GONE");
+    private final Consumer<Property> mSetGoneEndAction = new Consumer<>() {
+        @Override
+        public void accept(Property property) {
+            mKeyguardViewVisibilityAnimating = false;
+            mView.setVisibility(View.GONE);
+            log("CallbackSet Visibility to GONE");
+        }
     };
 
-    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
+    private final Runnable mSetVisibleEndRunnable = () -> {
         mKeyguardViewVisibilityAnimating = false;
         mView.setVisibility(View.VISIBLE);
         log("Callback Set Visibility to VISIBLE");
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 235a8bc..5f2afe8 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -294,6 +294,11 @@
 
         final CharSequence prevContentDescription = mView.getContentDescription();
         if (mShowLockIcon) {
+            if (wasShowingFpIcon) {
+                // fp icon was shown by UdfpsView, and now we still want to animate the transition
+                // in this drawable
+                mView.updateIcon(ICON_FINGERPRINT, false);
+            }
             mView.updateIcon(ICON_LOCK, false);
             mView.setContentDescription(mLockedLabel);
             mView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 179eb39..a3e7d71 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -35,6 +35,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.settingslib.Utils
 import com.android.systemui.animation.Interpolators
+import com.android.systemui.biometrics.AuthController
 import com.android.systemui.log.ScreenDecorationsLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.util.asIndenting
@@ -52,6 +53,7 @@
     val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     val mainExecutor: Executor,
     val logger: ScreenDecorationsLogger,
+    val authController: AuthController,
 ) : ScreenDecorations.DisplayCutoutView(context, pos) {
     private var showScanningAnim = false
     private val rimPaint = Paint()
@@ -102,7 +104,9 @@
     }
 
     override fun enableShowProtection(show: Boolean) {
-        val showScanningAnimNow = keyguardUpdateMonitor.isFaceDetectionRunning && show
+        val animationRequired =
+                keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing
+        val showScanningAnimNow = animationRequired && show
         if (showScanningAnimNow == showScanningAnim) {
             return
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 92344db..0999229 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -1005,9 +1005,11 @@
      * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
      */
     public boolean isRearFpsSupported() {
-        for (FingerprintSensorPropertiesInternal prop: mFpProps) {
-            if (prop.sensorType == TYPE_REAR) {
-                return true;
+        if (mFpProps != null) {
+            for (FingerprintSensorPropertiesInternal prop: mFpProps) {
+                if (prop.sensorType == TYPE_REAR) {
+                    return true;
+                }
             }
         }
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index d15a2af..6c62a39 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -39,7 +39,7 @@
     private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) =
         mainExecutor.execute {
             action?.let {
-                if (event.tracking || event.expanded) {
+                if (event.tracking || (event.expanded && event.fraction > 0)) {
                     Log.v(TAG, "Detected panel interaction, event: $event")
                     it.onPanelInteraction.run()
                     disable()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index aabdafb..7a23759 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -83,6 +83,7 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
@@ -151,6 +152,7 @@
     @NonNull private final DumpManager mDumpManager;
     @NonNull private final SystemUIDialogManager mDialogManager;
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     @NonNull private final VibratorHelper mVibrator;
     @NonNull private final FeatureFlags mFeatureFlags;
     @NonNull private final FalsingManager mFalsingManager;
@@ -818,7 +820,8 @@
             @NonNull AlternateBouncerInteractor alternateBouncerInteractor,
             @NonNull SecureSettings secureSettings,
             @NonNull InputManager inputManager,
-            @NonNull UdfpsUtils udfpsUtils) {
+            @NonNull UdfpsUtils udfpsUtils,
+            @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -882,6 +885,7 @@
                     }
                     return Unit.INSTANCE;
                 });
+        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
 
         final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
         mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
@@ -1141,6 +1145,7 @@
         if (!mOnFingerDown) {
             playStartHaptic();
 
+            mKeyguardFaceAuthInteractor.onUdfpsSensorTouched();
             if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
                 mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
index 6a6c3eb..0869351 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.common.shared.model
 
-import androidx.annotation.ColorRes
+import androidx.annotation.AttrRes
 
 /** Models an icon with a specific tint. */
 data class TintedIcon(
     val icon: Icon,
-    @ColorRes val tint: Int?,
+    @AttrRes val tint: Int?,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
index bcc5932..5c5723f 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.common.ui.binder
 
 import android.widget.ImageView
+import com.android.settingslib.Utils
 import com.android.systemui.common.shared.model.TintedIcon
 
 object TintedIconViewBinder {
@@ -33,7 +34,7 @@
         IconViewBinder.bind(tintedIcon.icon, view)
         view.imageTintList =
             if (tintedIcon.tint != null) {
-                view.resources.getColorStateList(tintedIcon.tint, view.context.theme)
+                Utils.getColorAttr(view.context, tintedIcon.tint)
             } else {
                 null
             }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt
index 26fc36d..81ed076 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt
@@ -19,10 +19,18 @@
 import android.util.MathUtils
 import android.view.MotionEvent
 
-/** Returns the distance from the position of this [MotionEvent] and the given coordinates. */
-fun MotionEvent.distanceFrom(
-    x: Float,
-    y: Float,
+/**
+ * Returns the distance from the raw position of this [MotionEvent] and the given coordinates.
+ * Because this is all expected to be in the coordinate space of the display and not the view,
+ * applying mutations to the view (such as scaling animations) does not affect the distance
+ * measured.
+ * @param xOnDisplay the x coordinate relative to the display
+ * @param yOnDisplay the y coordinate relative to the display
+ * @return distance from the raw position of this [MotionEvent] and the given coordinates
+ */
+fun MotionEvent.rawDistanceFrom(
+    xOnDisplay: Float,
+    yOnDisplay: Float,
 ): Float {
-    return MathUtils.dist(this.x, this.y, x, y)
+    return MathUtils.dist(this.rawX, this.rawY, xOnDisplay, yOnDisplay)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 88c0c50..4e62104 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -98,7 +98,8 @@
     }
 
     fun shouldShowFaceScanningAnim(): Boolean {
-        return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceDetectionRunning
+        return canShowFaceScanningAnim() &&
+                (keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing)
     }
 }
 
@@ -142,6 +143,7 @@
                 keyguardUpdateMonitor,
                 mainExecutor,
                 logger,
+                authController,
         )
         view.id = viewId
         view.setColor(tintColor)
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
index 7f44463..aca621b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
@@ -41,6 +41,7 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -195,7 +196,14 @@
          * Called by the monitor when this session is removed.
          */
         private void onRemoved() {
-            mCallbacks.forEach(callback -> callback.onRemoved());
+            mEventListeners.clear();
+            mGestureListeners.clear();
+            final Iterator<Callback> iter = mCallbacks.iterator();
+            while (iter.hasNext()) {
+                final Callback callback = iter.next();
+                callback.onRemoved();
+                iter.remove();
+            }
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 45e4bb63..ffacf01 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -65,7 +65,7 @@
     val FSI_ON_DND_UPDATE = releasedFlag(259130119, "fsi_on_dnd_update")
 
     // TODO(b/254512538): Tracking Bug
-    val INSTANT_VOICE_REPLY = releasedFlag(111, "instant_voice_reply")
+    val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply")
 
     // TODO(b/254512425): Tracking Bug
     val NOTIFICATION_MEMORY_MONITOR_ENABLED =
@@ -108,11 +108,6 @@
     val NOTIFICATION_SHELF_REFACTOR =
         unreleasedFlag(271161129, "notification_shelf_refactor")
 
-    // TODO(b/263414400): Tracking Bug
-    @JvmField
-    val NOTIFICATION_ANIMATE_BIG_PICTURE =
-        releasedFlag(120, "notification_animate_big_picture")
-
     @JvmField
     val ANIMATED_NOTIFICATION_SHADE_INSETS =
         releasedFlag(270682168, "animated_notification_shade_insets")
@@ -419,9 +414,6 @@
     // TODO(b/254512758): Tracking Bug
     @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
 
-    // TODO(b/270882464): Tracking Bug
-    val ENABLE_DOCK_SETUP_V2 = releasedFlag(1005, "enable_dock_setup_v2")
-
     // TODO(b/265045965): Tracking Bug
     val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
 
@@ -608,9 +600,6 @@
     // TODO(b/254512507): Tracking Bug
     val CHOOSER_UNBUNDLED = releasedFlag(1500, "chooser_unbundled")
 
-    // TODO(b/266983432) Tracking Bug
-    val SHARESHEET_CUSTOM_ACTIONS = releasedFlag(1501, "sharesheet_custom_actions")
-
     // TODO(b/266982749) Tracking Bug
     val SHARESHEET_RESELECTION_ACTION = releasedFlag(1502, "sharesheet_reselection_action")
 
@@ -704,6 +693,11 @@
     val KEYBOARD_BACKLIGHT_INDICATOR =
             unreleasedFlag(2601, "keyboard_backlight_indicator", teamfood = true)
 
+    // TODO(b/277192623): Tracking Bug
+    @JvmField
+    val KEYBOARD_EDUCATION =
+        unreleasedFlag(2603, "keyboard_education", teamfood = false)
+
     // TODO(b/272036292): Tracking Bug
     @JvmField
     val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 416b237..e24d0ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -53,7 +53,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
 import android.graphics.Matrix;
 import android.hardware.biometrics.BiometricSourceType;
 import android.media.AudioAttributes;
@@ -560,17 +559,9 @@
         @Override
         public void onUserSwitching(int userId) {
             if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
-            // Note that the mLockPatternUtils user has already been updated from setCurrentUser.
-            // We need to force a reset of the views, since lockNow (called by
-            // ActivityManagerService) will not reconstruct the keyguard if it is already showing.
             synchronized (KeyguardViewMediator.this) {
                 resetKeyguardDonePendingLocked();
-                if (mLockPatternUtils.isLockScreenDisabled(userId)) {
-                    // If we are switching to a user that has keyguard disabled, dismiss keyguard.
-                    dismiss(null /* callback */, null /* message */);
-                } else {
-                    resetStateLocked();
-                }
+                dismiss(null /* callback */, null /* message */);
                 adjustStatusBarLocked();
             }
         }
@@ -578,16 +569,9 @@
         @Override
         public void onUserSwitchComplete(int userId) {
             if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
-            if (userId != UserHandle.USER_SYSTEM) {
-                UserInfo info = UserManager.get(mContext).getUserInfo(userId);
-                // Don't try to dismiss if the user has Pin/Pattern/Password set
-                if (info == null || mLockPatternUtils.isSecure(userId)) {
-                    return;
-                } else if (info.isGuest() || info.isDemo()) {
-                    // If we just switched to a guest, try to dismiss keyguard.
-                    dismiss(null /* callback */, null /* message */);
-                }
-            }
+            // We are calling dismiss again and with a delay as there are race conditions
+            // in some scenarios caused by async layout listeners
+            new Handler().postDelayed(() -> dismiss(null /* callback */, null /* message */), 500);
         }
 
         @Override
@@ -2213,58 +2197,72 @@
     private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
         @Override
         public void handleMessage(Message msg) {
+            String message = "";
             switch (msg.what) {
                 case SHOW:
+                    message = "SHOW";
                     handleShow((Bundle) msg.obj);
                     break;
                 case HIDE:
+                    message = "HIDE";
                     handleHide();
                     break;
                 case RESET:
+                    message = "RESET";
                     handleReset();
                     break;
                 case VERIFY_UNLOCK:
+                    message = "VERIFY_UNLOCK";
                     Trace.beginSection("KeyguardViewMediator#handleMessage VERIFY_UNLOCK");
                     handleVerifyUnlock();
                     Trace.endSection();
                     break;
                 case NOTIFY_STARTED_GOING_TO_SLEEP:
+                    message = "NOTIFY_STARTED_GOING_TO_SLEEP";
                     handleNotifyStartedGoingToSleep();
                     break;
                 case NOTIFY_FINISHED_GOING_TO_SLEEP:
+                    message = "NOTIFY_FINISHED_GOING_TO_SLEEP";
                     handleNotifyFinishedGoingToSleep();
                     break;
                 case NOTIFY_STARTED_WAKING_UP:
+                    message = "NOTIFY_STARTED_WAKING_UP";
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage NOTIFY_STARTED_WAKING_UP");
                     handleNotifyStartedWakingUp();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE:
+                    message = "KEYGUARD_DONE";
                     Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE");
                     handleKeyguardDone();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE_DRAWING:
+                    message = "KEYGUARD_DONE_DRAWING";
                     Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE_DRAWING");
                     handleKeyguardDoneDrawing();
                     Trace.endSection();
                     break;
                 case SET_OCCLUDED:
+                    message = "SET_OCCLUDED";
                     Trace.beginSection("KeyguardViewMediator#handleMessage SET_OCCLUDED");
                     handleSetOccluded(msg.arg1 != 0, msg.arg2 != 0);
                     Trace.endSection();
                     break;
                 case KEYGUARD_TIMEOUT:
+                    message = "KEYGUARD_TIMEOUT";
                     synchronized (KeyguardViewMediator.this) {
                         doKeyguardLocked((Bundle) msg.obj);
                     }
                     break;
                 case DISMISS:
-                    final DismissMessage message = (DismissMessage) msg.obj;
-                    handleDismiss(message.getCallback(), message.getMessage());
+                    message = "DISMISS";
+                    final DismissMessage dismissMsg = (DismissMessage) msg.obj;
+                    handleDismiss(dismissMsg.getCallback(), dismissMsg.getMessage());
                     break;
                 case START_KEYGUARD_EXIT_ANIM:
+                    message = "START_KEYGUARD_EXIT_ANIM";
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
                     synchronized (KeyguardViewMediator.this) {
@@ -2282,21 +2280,25 @@
                     Trace.endSection();
                     break;
                 case CANCEL_KEYGUARD_EXIT_ANIM:
+                    message = "CANCEL_KEYGUARD_EXIT_ANIM";
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage CANCEL_KEYGUARD_EXIT_ANIM");
                     handleCancelKeyguardExitAnimation();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE_PENDING_TIMEOUT:
+                    message = "KEYGUARD_DONE_PENDING_TIMEOUT";
                     Trace.beginSection("KeyguardViewMediator#handleMessage"
                             + " KEYGUARD_DONE_PENDING_TIMEOUT");
                     Log.w(TAG, "Timeout while waiting for activity drawn!");
                     Trace.endSection();
                     break;
                 case SYSTEM_READY:
+                    message = "SYSTEM_READY";
                     handleSystemReady();
                     break;
             }
+            Log.d(TAG, "KeyguardViewMediator queue processing message: " + message);
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
new file mode 100644
index 0000000..a44df7e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
@@ -0,0 +1,35 @@
+/*
+ *   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.keyguard.dagger
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.NoopKeyguardFaceAuthInteractor
+import dagger.Binds
+import dagger.Module
+
+/**
+ * Module that provides bindings for face auth classes that are injected into SysUI components that
+ * are used across different SysUI variants, where face auth is not supported.
+ *
+ * Some variants that do not support face authentication can install this module to provide a no-op
+ * implementation of the interactor.
+ */
+@Module
+interface KeyguardFaceAuthNotSupportedModule {
+    @Binds
+    fun keyguardFaceAuthInteractor(impl: NoopKeyguardFaceAuthInteractor): KeyguardFaceAuthInteractor
+}
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 56e7398..c4fc883 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
@@ -44,6 +44,8 @@
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.log.SessionTracker
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.repository.UserRepository
 import java.io.PrintWriter
@@ -78,7 +80,7 @@
     val isAuthenticated: Flow<Boolean>
 
     /** Whether face auth can run at this point. */
-    val canRunFaceAuth: Flow<Boolean>
+    val canRunFaceAuth: StateFlow<Boolean>
 
     /** Provide the current status of face authentication. */
     val authenticationStatus: Flow<AuthenticationStatus>
@@ -87,10 +89,13 @@
     val detectionStatus: Flow<DetectionStatus>
 
     /** Current state of whether face authentication is locked out or not. */
-    val isLockedOut: Flow<Boolean>
+    val isLockedOut: StateFlow<Boolean>
 
     /** Current state of whether face authentication is running. */
-    val isAuthRunning: Flow<Boolean>
+    val isAuthRunning: StateFlow<Boolean>
+
+    /** Whether bypass is currently enabled */
+    val isBypassEnabled: Flow<Boolean>
 
     /**
      * Trigger face authentication.
@@ -126,6 +131,8 @@
     private val keyguardRepository: KeyguardRepository,
     private val keyguardInteractor: KeyguardInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
+    @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
     dumpManager: DumpManager,
 ) : DeviceEntryFaceAuthRepository, Dumpable {
     private var authCancellationSignal: CancellationSignal? = null
@@ -166,7 +173,7 @@
     override val isAuthenticated: Flow<Boolean>
         get() = _isAuthenticated
 
-    private val bypassEnabled: Flow<Boolean> =
+    override val isBypassEnabled: Flow<Boolean> =
         keyguardBypassController?.let {
             conflatedCallbackFlow {
                 val callback =
@@ -221,17 +228,19 @@
         // Face detection can run only when lockscreen bypass is enabled
         // & detection is supported & biometric unlock is not allowed.
         listOf(
-                canFaceAuthOrDetectRun(),
-                logAndObserve(bypassEnabled, "bypassEnabled"),
+                canFaceAuthOrDetectRun(faceDetectLog),
+                logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
                 logAndObserve(
                     biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
-                    "nonStrongBiometricIsNotAllowed"
+                    "nonStrongBiometricIsNotAllowed",
+                    faceDetectLog
                 ),
                 // We don't want to run face detect if it's not possible to authenticate with FP
                 // from the bouncer. UDFPS is the only fp sensor type that won't support this.
                 logAndObserve(
                     and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(),
-                    "udfpsAuthIsNotPossibleAnymore"
+                    "udfpsAuthIsNotPossibleAnymore",
+                    faceDetectLog
                 )
             )
             .reduce(::and)
@@ -243,6 +252,7 @@
                     cancelDetection()
                 }
             }
+            .logDiffsForTable(faceDetectLog, "", "canFaceDetectRun", false)
             .launchIn(applicationScope)
     }
 
@@ -251,26 +261,34 @@
             it == BiometricType.UNDER_DISPLAY_FINGERPRINT
         }
 
-    private fun canFaceAuthOrDetectRun(): Flow<Boolean> {
+    private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> {
         return listOf(
-                logAndObserve(biometricSettingsRepository.isFaceEnrolled, "isFaceEnrolled"),
+                logAndObserve(
+                    biometricSettingsRepository.isFaceEnrolled,
+                    "isFaceEnrolled",
+                    tableLogBuffer
+                ),
                 logAndObserve(
                     biometricSettingsRepository.isFaceAuthenticationEnabled,
-                    "isFaceAuthenticationEnabled"
+                    "isFaceAuthenticationEnabled",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     userRepository.userSwitchingInProgress.isFalse(),
-                    "userSwitchingNotInProgress"
+                    "userSwitchingNotInProgress",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     keyguardRepository.isKeyguardGoingAway.isFalse(),
-                    "keyguardNotGoingAway"
+                    "keyguardNotGoingAway",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     keyguardRepository.wakefulness
                         .map { WakefulnessModel.isSleepingOrStartingToSleep(it) }
                         .isFalse(),
-                    "deviceNotSleepingOrNotStartingToSleep"
+                    "deviceNotSleepingOrNotStartingToSleep",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     combine(
@@ -279,15 +297,18 @@
                     ) { a, b ->
                         !a || b
                     },
-                    "secureCameraNotActiveOrAltBouncerIsShowing"
+                    "secureCameraNotActiveOrAltBouncerIsShowing",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
-                    "isFaceAuthSupportedInCurrentPosture"
+                    "isFaceAuthSupportedInCurrentPosture",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
-                    "userHasNotLockedDownDevice"
+                    "userHasNotLockedDownDevice",
+                    tableLogBuffer
                 )
             )
             .reduce(::and)
@@ -296,20 +317,27 @@
     private fun observeFaceAuthGatingChecks() {
         // Face auth can run only if all of the gating conditions are true.
         listOf(
-                canFaceAuthOrDetectRun(),
-                logAndObserve(isLockedOut.isFalse(), "isNotLocked"),
+                canFaceAuthOrDetectRun(faceAuthLog),
+                logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog),
                 logAndObserve(
                     deviceEntryFingerprintAuthRepository.isLockedOut.isFalse(),
-                    "fpLockedOut"
+                    "fpLockedOut",
+                    faceAuthLog
                 ),
-                logAndObserve(trustRepository.isCurrentUserTrusted.isFalse(), "currentUserTrusted"),
+                logAndObserve(
+                    trustRepository.isCurrentUserTrusted.isFalse(),
+                    "currentUserTrusted",
+                    faceAuthLog
+                ),
                 logAndObserve(
                     biometricSettingsRepository.isNonStrongBiometricAllowed,
-                    "nonStrongBiometricIsAllowed"
+                    "nonStrongBiometricIsAllowed",
+                    faceAuthLog
                 ),
                 logAndObserve(
                     userRepository.selectedUserInfo.map { it.isPrimary },
-                    "userIsPrimaryUser"
+                    "userIsPrimaryUser",
+                    faceAuthLog
                 ),
             )
             .reduce(::and)
@@ -323,6 +351,7 @@
                     cancel()
                 }
             }
+            .logDiffsForTable(faceAuthLog, "", "canFaceAuthRun", false)
             .launchIn(applicationScope)
     }
 
@@ -337,7 +366,6 @@
 
             override fun onAuthenticationAcquired(acquireInfo: Int) {
                 _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
-                faceAuthLogger.authenticationAcquired(acquireInfo)
             }
 
             override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
@@ -398,7 +426,7 @@
 
     override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
         if (_isAuthRunning.value) {
-            faceAuthLogger.ignoredFaceAuthTrigger(uiEvent)
+            faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "face auth is currently running")
             return
         }
 
@@ -435,7 +463,16 @@
                 )
             }
         } else if (fallbackToDetection && canRunDetection.value) {
+            faceAuthLogger.ignoredFaceAuthTrigger(
+                uiEvent,
+                "face auth gating check is false, falling back to detection."
+            )
             detect()
+        } else {
+            faceAuthLogger.ignoredFaceAuthTrigger(
+                uiEvent,
+                "face auth & detect gating check is false"
+            )
         }
     }
 
@@ -464,7 +501,7 @@
     private val currentUserId: Int
         get() = userRepository.getSelectedUserInfo().id
 
-    fun cancelDetection() {
+    private fun cancelDetection() {
         detectCancellationSignal?.cancel()
         detectCancellationSignal = null
     }
@@ -488,10 +525,20 @@
         _isAuthRunning.value = false
     }
 
-    private fun logAndObserve(cond: Flow<Boolean>, loggingContext: String): Flow<Boolean> {
-        return cond.distinctUntilChanged().onEach {
-            faceAuthLogger.observedConditionChanged(it, loggingContext)
-        }
+    private fun logAndObserve(
+        cond: Flow<Boolean>,
+        conditionName: String,
+        logBuffer: TableLogBuffer
+    ): Flow<Boolean> {
+        return cond
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                logBuffer,
+                columnName = conditionName,
+                columnPrefix = "",
+                initialValue = false
+            )
+            .onEach { faceAuthLogger.observedConditionChanged(it, conditionName) }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt
new file mode 100644
index 0000000..6c23032
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ *   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.keyguard.data.repository
+
+import javax.inject.Qualifier
+
+/** Face auth logs in table format. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FaceAuthTableLog
diff --git a/core/java/com/android/internal/expresslog/Utils.java b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt
similarity index 72%
rename from core/java/com/android/internal/expresslog/Utils.java
rename to packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt
index d82192f..342064f 100644
--- a/core/java/com/android/internal/expresslog/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt
@@ -14,8 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.internal.expresslog;
+package com.android.systemui.keyguard.data.repository
 
-final class Utils {
-    static native long hashString(String stringToHash);
-}
+import javax.inject.Qualifier
+
+/** Face detect logs in table format. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FaceDetectTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
index 3c66f24..ef8b401 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -17,8 +17,17 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.SystemUIKeyguardFaceAuthInteractor
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 
 @Module
 interface KeyguardFaceAuthModule {
@@ -27,5 +36,31 @@
         impl: DeviceEntryFaceAuthRepositoryImpl
     ): DeviceEntryFaceAuthRepository
 
+    @Binds
+    @IntoMap
+    @ClassKey(SystemUIKeyguardFaceAuthInteractor::class)
+    fun bind(impl: SystemUIKeyguardFaceAuthInteractor): CoreStartable
+
+    @Binds
+    fun keyguardFaceAuthInteractor(
+        impl: SystemUIKeyguardFaceAuthInteractor
+    ): KeyguardFaceAuthInteractor
+
     @Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        @FaceAuthTableLog
+        fun provideFaceAuthTableLog(factory: TableLogBufferFactory): TableLogBuffer {
+            return factory.create("FaceAuthTableLog", 100)
+        }
+
+        @Provides
+        @SysUISingleton
+        @FaceDetectTableLog
+        fun provideFaceDetectTableLog(factory: TableLogBufferFactory): TableLogBuffer {
+            return factory.create("FaceDetectTableLog", 100)
+        }
+    }
 }
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
new file mode 100644
index 0000000..06ae11fe8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -0,0 +1,78 @@
+/*
+ *   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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Interactor that exposes API to get the face authentication status and handle any events that can
+ * cause face authentication to run.
+ */
+interface KeyguardFaceAuthInteractor {
+
+    /** Current authentication status */
+    val authenticationStatus: Flow<AuthenticationStatus>
+
+    /** Current detection status */
+    val detectionStatus: Flow<DetectionStatus>
+
+    /** Can face auth be run right now */
+    fun canFaceAuthRun(): Boolean
+
+    /** Whether face auth is currently running or not. */
+    fun isRunning(): Boolean
+
+    /** Whether face auth is in lock out state. */
+    fun isLockedOut(): Boolean
+
+    /**
+     * Register listener for use from code that cannot use [authenticationStatus] or
+     * [detectionStatus]
+     */
+    fun registerListener(listener: FaceAuthenticationListener)
+
+    /** Unregister previously registered listener */
+    fun unregisterListener(listener: FaceAuthenticationListener)
+
+    /** Whether the face auth interactor is enabled or not. */
+    fun isEnabled(): Boolean
+
+    fun onUdfpsSensorTouched()
+    fun onAssistantTriggeredOnLockScreen()
+    fun onDeviceLifted()
+    fun onQsExpansionStared()
+    fun onNotificationPanelClicked()
+    fun onSwipeUpOnBouncer()
+}
+
+/**
+ * Listener that can be registered with the [KeyguardFaceAuthInteractor] to receive updates about
+ * face authentication & detection updates.
+ *
+ * This is present to make it easier for use the new face auth API for code that cannot use
+ * [KeyguardFaceAuthInteractor.authenticationStatus] or [KeyguardFaceAuthInteractor.detectionStatus]
+ * flows.
+ */
+interface FaceAuthenticationListener {
+    /** Receive face authentication status updates */
+    fun onAuthenticationStatusChanged(status: AuthenticationStatus)
+
+    /** Receive status updates whenever face detection runs */
+    fun onDetectionStatusChanged(status: DetectionStatus)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index aabd212..da0ada1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -78,6 +78,14 @@
     val primaryBouncerToGoneTransition: Flow<TransitionStep> =
         repository.transition(PRIMARY_BOUNCER, GONE)
 
+    /** OFF->LOCKSCREEN transition information. */
+    val offToLockscreenTransition: Flow<TransitionStep> =
+        repository.transition(KeyguardState.OFF, LOCKSCREEN)
+
+    /** DOZING->LOCKSCREEN transition information. */
+    val dozingToLockscreenTransition: Flow<TransitionStep> =
+        repository.transition(KeyguardState.DOZING, LOCKSCREEN)
+
     /**
      * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
      * Lockscreen (0f).
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
new file mode 100644
index 0000000..cad40aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -0,0 +1,62 @@
+/*
+ *   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.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * Implementation of the interactor that noops all face auth operations.
+ *
+ * This is required for SystemUI variants that do not support face authentication but still inject
+ * other SysUI components that depend on [KeyguardFaceAuthInteractor]
+ */
+@SysUISingleton
+class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor {
+    override val authenticationStatus: Flow<AuthenticationStatus>
+        get() = emptyFlow()
+    override val detectionStatus: Flow<DetectionStatus>
+        get() = emptyFlow()
+
+    override fun canFaceAuthRun(): Boolean = false
+
+    override fun isRunning(): Boolean = false
+
+    override fun isLockedOut(): Boolean = false
+
+    override fun isEnabled() = false
+
+    override fun registerListener(listener: FaceAuthenticationListener) {}
+
+    override fun unregisterListener(listener: FaceAuthenticationListener) {}
+
+    override fun onUdfpsSensorTouched() {}
+
+    override fun onAssistantTriggeredOnLockScreen() {}
+
+    override fun onDeviceLifted() {}
+
+    override fun onQsExpansionStared() {}
+
+    override fun onNotificationPanelClicked() {}
+
+    override fun onSwipeUpOnBouncer() {}
+}
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
new file mode 100644
index 0000000..20ebb71
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -0,0 +1,200 @@
+/*
+ *   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.keyguard.domain.interactor
+
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.util.kotlin.pairwise
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+/**
+ * Encapsulates business logic related face authentication being triggered for device entry from
+ * SystemUI Keyguard.
+ */
+@SysUISingleton
+class SystemUIKeyguardFaceAuthInteractor
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    private val repository: DeviceEntryFaceAuthRepository,
+    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val featureFlags: FeatureFlags,
+    private val faceAuthenticationLogger: FaceAuthenticationLogger,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) : CoreStartable, KeyguardFaceAuthInteractor {
+
+    private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
+
+    override fun start() {
+        if (!isEnabled()) {
+            return
+        }
+        // This is required because fingerprint state required for the face auth repository is
+        // backed by KeyguardUpdateMonitor. KeyguardUpdateMonitor constructor accesses the biometric
+        // state which makes lazy injection not an option.
+        keyguardUpdateMonitor.setFaceAuthInteractor(this)
+        observeFaceAuthStateUpdates()
+        faceAuthenticationLogger.interactorStarted()
+        primaryBouncerInteractor.isShowing
+            .whenItFlipsToTrue()
+            .onEach {
+                faceAuthenticationLogger.bouncerVisibilityChanged()
+                runFaceAuth(
+                    FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN,
+                    fallbackToDetect = true
+                )
+            }
+            .launchIn(applicationScope)
+
+        alternateBouncerInteractor.isVisible
+            .whenItFlipsToTrue()
+            .onEach {
+                faceAuthenticationLogger.alternateBouncerVisibilityChanged()
+                runFaceAuth(
+                    FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
+                    fallbackToDetect = false
+                )
+            }
+            .launchIn(applicationScope)
+
+        merge(
+                keyguardTransitionInteractor.aodToLockscreenTransition,
+                keyguardTransitionInteractor.offToLockscreenTransition,
+                keyguardTransitionInteractor.dozingToLockscreenTransition
+            )
+            .filter { it.transitionState == TransitionState.STARTED }
+            .onEach {
+                faceAuthenticationLogger.lockscreenBecameVisible(it)
+                runFaceAuth(
+                    FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED,
+                    fallbackToDetect = true
+                )
+            }
+            .launchIn(applicationScope)
+    }
+
+    override fun onSwipeUpOnBouncer() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false)
+    }
+
+    override fun onNotificationPanelClicked() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true)
+    }
+
+    override fun onQsExpansionStared() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true)
+    }
+
+    override fun onDeviceLifted() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, true)
+    }
+
+    override fun onAssistantTriggeredOnLockScreen() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED, true)
+    }
+
+    override fun onUdfpsSensorTouched() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false)
+    }
+
+    override fun registerListener(listener: FaceAuthenticationListener) {
+        listeners.add(listener)
+    }
+
+    override fun unregisterListener(listener: FaceAuthenticationListener) {
+        listeners.remove(listener)
+    }
+
+    override fun isLockedOut(): Boolean = repository.isLockedOut.value
+
+    override fun isRunning(): Boolean = repository.isAuthRunning.value
+
+    override fun canFaceAuthRun(): Boolean = repository.canRunFaceAuth.value
+
+    override fun isEnabled(): Boolean {
+        return featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)
+    }
+
+    /** Provide the status of face authentication */
+    override val authenticationStatus = repository.authenticationStatus
+
+    /** Provide the status of face detection */
+    override val detectionStatus = repository.detectionStatus
+
+    private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
+        if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
+            applicationScope.launch {
+                faceAuthenticationLogger.authRequested(uiEvent)
+                repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
+            }
+        } else {
+            faceAuthenticationLogger.ignoredFaceAuthTrigger(
+                uiEvent,
+                ignoredReason = "Skipping face auth request because feature flag is false"
+            )
+        }
+    }
+
+    private fun observeFaceAuthStateUpdates() {
+        authenticationStatus
+            .onEach { authStatusUpdate ->
+                listeners.forEach { it.onAuthenticationStatusChanged(authStatusUpdate) }
+            }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
+        detectionStatus
+            .onEach { detectionStatusUpdate ->
+                listeners.forEach { it.onDetectionStatusChanged(detectionStatusUpdate) }
+            }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
+    }
+
+    companion object {
+        const val TAG = "KeyguardFaceAuthInteractor"
+    }
+}
+
+// Extension method that filters a generic Boolean flow to one that emits
+// whenever there is flip from false -> true
+private fun Flow<Boolean>.whenItFlipsToTrue(): Flow<Boolean> {
+    return this.pairwise()
+        .filter { pair -> !pair.previousValue && pair.newValue }
+        .map { it.newValue }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
index 779095c..5745d6a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
@@ -26,7 +26,7 @@
 import androidx.core.animation.ObjectAnimator
 import com.android.systemui.R
 import com.android.systemui.animation.Expandable
-import com.android.systemui.common.ui.view.distanceFrom
+import com.android.systemui.common.ui.view.rawDistanceFrom
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
@@ -41,14 +41,14 @@
 
     private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong()
     private var longPressAnimator: ViewPropertyAnimator? = null
-    private val down: PointF by lazy { PointF() }
+    private val downDisplayCoords: PointF by lazy { PointF() }
 
     @SuppressLint("ClickableViewAccessibility")
     override fun onTouch(v: View, event: MotionEvent): Boolean {
         return when (event.actionMasked) {
             MotionEvent.ACTION_DOWN ->
                 if (viewModel.configKey != null) {
-                    down.set(event.x, event.y)
+                    downDisplayCoords.set(event.rawX, event.rawY)
                     if (isUsingAccurateTool(event)) {
                         // For accurate tool types (stylus, mouse, etc.), we don't require a
                         // long-press.
@@ -81,7 +81,13 @@
                 if (!isUsingAccurateTool(event)) {
                     // Moving too far while performing a long-press gesture cancels that
                     // gesture.
-                    if (event.distanceFrom(down.x, down.y) > ViewConfiguration.getTouchSlop()) {
+                    if (
+                        event
+                            .rawDistanceFrom(
+                                downDisplayCoords.x,
+                                downDisplayCoords.y,
+                            ) > ViewConfiguration.getTouchSlop()
+                    ) {
                         cancel()
                     }
                 }
@@ -94,7 +100,7 @@
                     // the pointer performs a click.
                     if (
                         viewModel.configKey != null &&
-                            event.distanceFrom(down.x, down.y) <=
+                            event.rawDistanceFrom(downDisplayCoords.x, downDisplayCoords.y) <=
                                 ViewConfiguration.getTouchSlop() &&
                             falsingManager?.isFalseTap(FalsingManager.NO_PENALTY) == false
                     ) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
index ad3fb63..c54203c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
@@ -21,7 +21,7 @@
 import android.view.View
 import android.view.ViewConfiguration
 import com.android.systemui.animation.view.LaunchableLinearLayout
-import com.android.systemui.common.ui.view.distanceFrom
+import com.android.systemui.common.ui.view.rawDistanceFrom
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
 
 class KeyguardSettingsButtonOnTouchListener(
@@ -29,18 +29,20 @@
     private val viewModel: KeyguardSettingsMenuViewModel,
 ) : View.OnTouchListener {
 
-    private val downPosition = PointF()
+    private val downPositionDisplayCoords = PointF()
 
     override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
         when (motionEvent.actionMasked) {
             MotionEvent.ACTION_DOWN -> {
                 view.isPressed = true
-                downPosition.set(motionEvent.x, motionEvent.y)
+                downPositionDisplayCoords.set(motionEvent.rawX, motionEvent.rawY)
                 viewModel.onTouchGestureStarted()
             }
             MotionEvent.ACTION_UP -> {
                 view.isPressed = false
-                val distanceMoved = motionEvent.distanceFrom(downPosition.x, downPosition.y)
+                val distanceMoved =
+                    motionEvent
+                        .rawDistanceFrom(downPositionDisplayCoords.x, downPositionDisplayCoords.y)
                 val isClick = distanceMoved < ViewConfiguration.getTouchSlop()
                 viewModel.onTouchGestureEnded(isClick)
                 if (isClick) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index f7355d5..7f6e4a9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -4,6 +4,7 @@
 import android.hardware.face.FaceSensorPropertiesInternal
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.log.dagger.FaceAuthLog
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel.DEBUG
@@ -27,15 +28,15 @@
 constructor(
     @FaceAuthLog private val logBuffer: LogBuffer,
 ) {
-    fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent) {
+    fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent, ignoredReason: String) {
         logBuffer.log(
             TAG,
             DEBUG,
-            { str1 = uiEvent.reason },
             {
-                "Ignoring trigger because face auth is currently running. " +
-                    "Trigger reason: $str1"
-            }
+                str1 = uiEvent.reason
+                str2 = ignoredReason
+            },
+            { "Ignoring trigger because $str2, Trigger reason: $str1" }
         )
     }
 
@@ -135,15 +136,6 @@
         logBuffer.log(TAG, DEBUG, "Face authentication failed")
     }
 
-    fun authenticationAcquired(acquireInfo: Int) {
-        logBuffer.log(
-            TAG,
-            DEBUG,
-            { int1 = acquireInfo },
-            { "Face acquired during face authentication: acquireInfo: $int1 " }
-        )
-    }
-
     fun authenticationError(
         errorCode: Int,
         errString: CharSequence?,
@@ -217,4 +209,34 @@
     fun cancellingFaceAuth() {
         logBuffer.log(TAG, DEBUG, "cancelling face auth because a gating condition became false")
     }
+
+    fun interactorStarted() {
+        logBuffer.log(TAG, DEBUG, "KeyguardFaceAuthInteractor started")
+    }
+
+    fun bouncerVisibilityChanged() {
+        logBuffer.log(TAG, DEBUG, "Triggering face auth because primary bouncer is visible")
+    }
+
+    fun alternateBouncerVisibilityChanged() {
+        logBuffer.log(TAG, DEBUG, "Triggering face auth because alternate bouncer is visible")
+    }
+
+    fun lockscreenBecameVisible(transitionStep: TransitionStep?) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { str1 = "$transitionStep" },
+            { "Triggering face auth because lockscreen became visible due to transition: $str1" }
+        )
+    }
+
+    fun authRequested(uiEvent: FaceAuthUiEvent) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { str1 = "$uiEvent" },
+            { "Requesting face auth for trigger: $str1" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index dbc2a5e..b29b588 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -19,7 +19,7 @@
 import android.content.Context
 import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
-import androidx.annotation.ColorRes
+import androidx.annotation.AttrRes
 import androidx.annotation.DrawableRes
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.ContentDescription
@@ -108,7 +108,7 @@
 data class IconInfo(
     val contentDescription: ContentDescription,
     val icon: MediaTttIcon,
-    @ColorRes val tint: Int?,
+    @AttrRes val tint: Int?,
     /**
      * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 26b0e8d..b9ef916 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -55,6 +55,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
+import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -173,7 +174,7 @@
                 }
             };
 
-
+    private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
     private final Context mContext;
     private final UserTracker mUserTracker;
     private final OverviewProxyService mOverviewProxyService;
@@ -901,6 +902,10 @@
                 Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev);
             }
 
+            // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
+            // ACTION_DOWN, in that case we should just reuse the old instance.
+            mVelocityTracker.clear();
+
             // Verify if this is in within the touch region and we aren't in immersive mode, and
             // either the bouncer is showing or the notification panel is hidden
             mInputEventReceiver.setBatchingEnabled(false);
@@ -1027,11 +1032,30 @@
 
     private void dispatchToBackAnimation(MotionEvent event) {
         if (mBackAnimation != null) {
+            mVelocityTracker.addMovement(event);
+
+            final float velocityX;
+            final float velocityY;
+            if (event.getAction() == MotionEvent.ACTION_UP) {
+                // Compute the current velocity is expensive (see computeCurrentVelocity), so we
+                // are only doing it when the user completes the gesture.
+                int unitPixelPerSecond = 1000;
+                int maxVelocity = mViewConfiguration.getScaledMaximumFlingVelocity();
+                mVelocityTracker.computeCurrentVelocity(unitPixelPerSecond, maxVelocity);
+                velocityX = mVelocityTracker.getXVelocity();
+                velocityY = mVelocityTracker.getYVelocity();
+            } else {
+                velocityX = Float.NaN;
+                velocityY = Float.NaN;
+            }
+
             mBackAnimation.onBackMotion(
-                    event.getX(),
-                    event.getY(),
-                    event.getActionMasked(),
-                    mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT);
+                    /* touchX = */ event.getX(),
+                    /* touchY = */ event.getY(),
+                    /* velocityX = */ velocityX,
+                    /* velocityY = */ velocityY,
+                    /* keyAction = */ event.getActionMasked(),
+                    /* swipeEdge = */ mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index c2c1306..a765702 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -18,6 +18,10 @@
 
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_CONFIRMATION;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_LOW_WARNING;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason;
+
 import android.app.Dialog;
 import android.app.KeyguardManager;
 import android.app.Notification;
@@ -691,7 +695,7 @@
             d.setTitle(R.string.battery_saver_confirmation_title);
             d.setPositiveButton(R.string.battery_saver_confirmation_ok,
                     (dialog, which) -> {
-                        setSaverMode(true, false);
+                        setSaverMode(true, false, SAVER_ENABLED_CONFIRMATION);
                         logEvent(BatteryWarningEvents.LowBatteryWarningEvent.SAVER_CONFIRM_OK);
                     });
             d.setNegativeButton(android.R.string.cancel, (dialog, which) ->
@@ -790,8 +794,9 @@
         return builder;
     }
 
-    private void setSaverMode(boolean mode, boolean needFirstTimeWarning) {
-        BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning);
+    private void setSaverMode(boolean mode, boolean needFirstTimeWarning,
+            @SaverManualEnabledReason int reason) {
+        BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning, reason);
     }
 
     private void startBatterySaverSchedulePage() {
@@ -839,7 +844,7 @@
             } else if (action.equals(ACTION_START_SAVER)) {
                 logEvent(BatteryWarningEvents
                         .LowBatteryWarningEvent.LOW_BATTERY_NOTIFICATION_TURN_ON);
-                setSaverMode(true, true);
+                setSaverMode(true, true, SAVER_ENABLED_LOW_WARNING);
                 dismissLowBatteryNotification();
             } else if (action.equals(ACTION_SHOW_START_SAVER_CONFIRMATION)) {
                 dismissLowBatteryNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 57b479e..856c64a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -20,8 +20,6 @@
 import android.os.Build;
 import android.provider.Settings;
 
-import com.android.internal.logging.InstanceId;
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
@@ -55,11 +53,9 @@
         return tiles;
     }
 
-    void warn(String message, Throwable t);
     Context getContext();
     Context getUserContext();
     int getUserId();
-    UiEventLogger getUiEventLogger();
     Collection<QSTile> getTiles();
     void addCallback(Callback callback);
     void removeCallback(Callback callback);
@@ -107,8 +103,6 @@
 
     int indexOf(String tileSpec);
 
-    InstanceId getNewInstanceId();
-
     interface Callback {
         void onTilesChanged();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
index 14acb4b..67927a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
@@ -19,8 +19,6 @@
 import android.content.ComponentName
 import android.content.Context
 import androidx.annotation.GuardedBy
-import com.android.internal.logging.InstanceId
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dump.DumpManager
@@ -39,10 +37,9 @@
 
 /**
  * Adapter to determine what real class to use for classes that depend on [QSHost].
- *
  * * When [Flags.QS_PIPELINE_NEW_HOST] is off, all calls will be routed to [QSTileHost].
  * * When [Flags.QS_PIPELINE_NEW_HOST] is on, calls regarding the current set of tiles will be
- *   routed to [CurrentTilesInteractor]. Other calls (like [warn]) will still be routed to
+ *   routed to [CurrentTilesInteractor]. Other calls (like [createTileView]) will still be routed to
  *   [QSTileHost].
  *
  * This routing also includes dumps.
@@ -71,10 +68,7 @@
     init {
         scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() }
         // Redirect dump to the correct host (needed for CTS tests)
-        dumpManager.registerCriticalDumpable(
-            TAG,
-            if (useNewHost) interactor else qsTileHost
-        )
+        dumpManager.registerCriticalDumpable(TAG, if (useNewHost) interactor else qsTileHost)
     }
 
     override fun getTiles(): Collection<QSTile> {
@@ -103,10 +97,7 @@
 
     override fun addCallback(callback: QSHost.Callback) {
         if (useNewHost) {
-            val job =
-                scope.launch {
-                    interactor.currentTiles.collect { callback.onTilesChanged() }
-                }
+            val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } }
             synchronized(callbacksMap) { callbacksMap.put(callback, job) }
         } else {
             qsTileHost.addCallback(callback)
@@ -147,10 +138,7 @@
 
     override fun addTile(component: ComponentName, end: Boolean) {
         if (useNewHost) {
-            interactor.addTile(
-                TileSpec.create(component),
-                if (end) POSITION_AT_END else 0
-            )
+            interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0)
         } else {
             qsTileHost.addTile(component, end)
         }
@@ -164,10 +152,6 @@
         }
     }
 
-    override fun warn(message: String?, t: Throwable?) {
-        qsTileHost.warn(message, t)
-    }
-
     override fun getContext(): Context {
         return if (useNewHost) {
             context
@@ -192,10 +176,6 @@
         }
     }
 
-    override fun getUiEventLogger(): UiEventLogger {
-        return qsTileHost.uiEventLogger
-    }
-
     override fun createTileView(
         themedContext: Context?,
         tile: QSTile?,
@@ -219,8 +199,4 @@
     override fun indexOf(tileSpec: String): Int {
         return specs.indexOf(tileSpec)
     }
-
-    override fun getNewInstanceId(): InstanceId {
-        return qsTileHost.newInstanceId
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 0ca8973..59b94b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -29,9 +29,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.InstanceId;
-import com.android.internal.logging.InstanceIdSequence;
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.Dumpable;
 import com.android.systemui.ProtoDumpable;
 import com.android.systemui.R;
@@ -91,7 +88,6 @@
         PanelInteractor, CustomTileAddedRepository {
     private static final String TAG = "QSTileHost";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final int MAX_QS_INSTANCE_ID = 1 << 20;
 
     // Shared prefs that hold tile lifecycle info.
     @VisibleForTesting
@@ -103,8 +99,6 @@
     private final TunerService mTunerService;
     private final PluginManager mPluginManager;
     private final QSLogger mQSLogger;
-    private final UiEventLogger mUiEventLogger;
-    private final InstanceIdSequence mInstanceIdSequence;
     private final CustomTileStatePersister mCustomTileStatePersister;
     private final Executor mMainExecutor;
     private final UserFileManager mUserFileManager;
@@ -137,7 +131,6 @@
             Provider<AutoTileManager> autoTiles,
             Optional<CentralSurfaces> centralSurfacesOptional,
             QSLogger qsLogger,
-            UiEventLogger uiEventLogger,
             UserTracker userTracker,
             SecureSettings secureSettings,
             CustomTileStatePersister customTileStatePersister,
@@ -150,13 +143,11 @@
         mTunerService = tunerService;
         mPluginManager = pluginManager;
         mQSLogger = qsLogger;
-        mUiEventLogger = uiEventLogger;
         mMainExecutor = mainExecutor;
         mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
         mUserFileManager = userFileManager;
         mFeatureFlags = featureFlags;
 
-        mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
         mCentralSurfacesOptional = centralSurfacesOptional;
 
         mQsFactories.add(defaultFactory);
@@ -175,11 +166,6 @@
         });
     }
 
-    @Override
-    public InstanceId getNewInstanceId() {
-        return mInstanceIdSequence.newInstanceId();
-    }
-
     public void destroy() {
         mTiles.values().forEach(tile -> tile.destroy());
         mAutoTiles.destroy();
@@ -207,11 +193,6 @@
     }
 
     @Override
-    public UiEventLogger getUiEventLogger() {
-        return mUiEventLogger;
-    }
-
-    @Override
     public void addCallback(Callback callback) {
         mCallbacks.add(callback);
     }
@@ -227,11 +208,6 @@
     }
 
     @Override
-    public void warn(String message, Throwable t) {
-        // already logged
-    }
-
-    @Override
     public void collapsePanels() {
         mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateCollapsePanels);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt
new file mode 100644
index 0000000..fc739ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt
@@ -0,0 +1,44 @@
+/*
+ * 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
+
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+interface QsEventLogger : UiEventLogger {
+    fun getNewInstanceId(): InstanceId
+}
+
+@SysUISingleton
+class QsEventLoggerImpl
+@Inject
+constructor(
+    uiEventLogger: UiEventLogger,
+) : QsEventLogger, UiEventLogger by uiEventLogger {
+
+    companion object {
+        private const val MAX_QS_INSTANCE_ID = 1 shl 20
+    }
+
+    val sequence = InstanceIdSequence(MAX_QS_INSTANCE_ID)
+    override fun getNewInstanceId(): InstanceId {
+        return sequence.newInstanceId()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
index 3ddd9f1c..1f63f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
@@ -21,6 +21,8 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QSHostAdapter
 import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.QsEventLoggerImpl
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
@@ -34,8 +36,12 @@
 
     @Binds fun provideQsHost(controllerImpl: QSHostAdapter): QSHost
 
+    @Binds fun provideEventLogger(impl: QsEventLoggerImpl): QsEventLogger
+
     @Module
     companion object {
+        private const val MAX_QS_INSTANCE_ID = 1 shl 20
+
         @Provides
         @JvmStatic
         fun providePanelInteractor(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index d4854e1..897b0e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -59,17 +59,20 @@
 import com.android.systemui.plugins.qs.QSTile.State;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.DisplayTracker;
 
+import dagger.Lazy;
+
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
+
 
 public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
     public static final String PREFIX = "custom(";
@@ -111,6 +114,7 @@
 
     private CustomTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             Looper backgroundLooper,
             Handler mainHandler,
             FalsingManager falsingManager,
@@ -124,7 +128,7 @@
             TileServices tileServices,
             DisplayTracker displayTracker
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mTileServices = tileServices;
         mWindowManager = WindowManagerGlobal.getWindowManagerService();
@@ -561,6 +565,7 @@
 
     public static class Builder {
         final Lazy<QSHost> mQSHostLazy;
+        final QsEventLogger mUiEventLogger;
         final Looper mBackgroundLooper;
         final Handler mMainHandler;
         private final FalsingManager mFalsingManager;
@@ -578,6 +583,7 @@
         @Inject
         public Builder(
                 Lazy<QSHost> hostLazy,
+                QsEventLogger uiEventLogger,
                 @Background Looper backgroundLooper,
                 @Main Handler mainHandler,
                 FalsingManager falsingManager,
@@ -590,6 +596,7 @@
                 DisplayTracker displayTracker
         ) {
             mQSHostLazy = hostLazy;
+            mUiEventLogger = uiEventLogger;
             mBackgroundLooper = backgroundLooper;
             mMainHandler = mainHandler;
             mFalsingManager = falsingManager;
@@ -620,6 +627,7 @@
             String action = getAction(mSpec);
             return new CustomTile(
                     mQSHostLazy.get(),
+                    mUiEventLogger,
                     mBackgroundLooper,
                     mMainHandler,
                     mFalsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 49ba508..2a9e7d0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -66,6 +66,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSEvent;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SideLabelTileLayout;
 import com.android.systemui.qs.logging.QSLogger;
 
@@ -179,6 +180,7 @@
 
     protected QSTileImpl(
             QSHost host,
+            QsEventLogger uiEventLogger,
             Looper backgroundLooper,
             Handler mainHandler,
             FalsingManager falsingManager,
@@ -189,8 +191,8 @@
     ) {
         mHost = host;
         mContext = host.getContext();
-        mInstanceId = host.getNewInstanceId();
-        mUiEventLogger = host.getUiEventLogger();
+        mInstanceId = uiEventLogger.getNewInstanceId();
+        mUiEventLogger = uiEventLogger;
 
         mUiHandler = mainHandler;
         mHandler = new H(backgroundLooper);
@@ -633,7 +635,6 @@
             } catch (Throwable t) {
                 final String error = "Error in " + name;
                 Log.w(TAG, error, t);
-                mHost.warn(error, t);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 92a83bb..30765f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -45,15 +45,18 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.GlobalSettings;
 
+import dagger.Lazy;
+
 import javax.inject.Inject;
 
-import dagger.Lazy;
+
 
 /** Quick settings tile: Airplane mode **/
 public class AirplaneModeTile extends QSTileImpl<BooleanState> {
@@ -69,6 +72,7 @@
     @Inject
     public AirplaneModeTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -81,7 +85,7 @@
             GlobalSettings globalSettings,
             UserTracker userTracker
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBroadcastDispatcher = broadcastDispatcher;
         mLazyConnectivityManager = lazyConnectivityManager;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
index 2ca452e..c709969 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -22,6 +22,7 @@
 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.settings.UserTracker
@@ -31,6 +32,7 @@
 
 class AlarmTile @Inject constructor(
     host: QSHost,
+    uiEventLogger: QsEventLogger,
     @Background backgroundLooper: Looper,
     @Main mainHandler: Handler,
     falsingManager: FalsingManager,
@@ -42,6 +44,7 @@
     nextAlarmController: NextAlarmController
 ) : QSTileImpl<QSTile.State>(
     host,
+    uiEventLogger,
     backgroundLooper,
     mainHandler,
     falsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index 027a464..a444e76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -62,6 +63,7 @@
     @Inject
     public BatterySaverTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -72,7 +74,7 @@
             BatteryController batteryController,
             SecureSettings secureSettings
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBatteryController = batteryController;
         mBatteryController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 08fe270..30218a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -47,6 +47,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 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.statusbar.policy.BluetoothController;
@@ -74,6 +75,7 @@
     @Inject
     public BluetoothTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -83,7 +85,7 @@
             QSLogger qsLogger,
             BluetoothController bluetoothController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = bluetoothController;
         mController.observe(getLifecycle(), mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
index 93e5f1e..65ef6b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.FalsingManager;
 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.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -48,7 +49,9 @@
     public static final String TILE_SPEC = "cameratoggle";
 
     @Inject
-    protected CameraToggleTile(QSHost host,
+    protected CameraToggleTile(
+            QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             MetricsLogger metricsLogger,
@@ -58,7 +61,7 @@
             QSLogger qsLogger,
             IndividualSensorPrivacyController sensorPrivacyController,
             KeyguardStateController keyguardStateController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger, sensorPrivacyController,
                 keyguardStateController);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 8d98481..54376fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -47,6 +47,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 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.statusbar.connectivity.NetworkController;
@@ -84,6 +85,7 @@
     @Inject
     public CastTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -97,7 +99,7 @@
             HotspotController hotspotController,
             DialogLaunchAnimator dialogLaunchAnimator
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = castController;
         mKeyguard = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
index b6205d5..cf9e346 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -56,6 +57,7 @@
     @Inject
     public ColorCorrectionTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -66,7 +68,7 @@
             UserTracker userTracker,
             SecureSettings secureSettings
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
 
         mSetting = new SettingObserver(secureSettings, mHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 9a44e83..4ecde61 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -38,6 +38,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -55,6 +56,7 @@
     @Inject
     public ColorInversionTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -65,7 +67,7 @@
             UserTracker userTracker,
             SecureSettings secureSettings
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
 
         mSetting = new SettingObserver(secureSettings, mHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index add517e..e769b5e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -38,6 +38,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 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.statusbar.phone.SystemUIDialog;
@@ -58,6 +59,7 @@
     @Inject
     public DataSaverTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -68,7 +70,7 @@
             DataSaverController dataSaverController,
             DialogLaunchAnimator dialogLaunchAnimator
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mDataSaverController = dataSaverController;
         mDialogLaunchAnimator = dialogLaunchAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index 01164fb..ddaff3b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -40,6 +40,7 @@
 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 java.util.concurrent.atomic.AtomicBoolean
@@ -47,6 +48,7 @@
 
 class DeviceControlsTile @Inject constructor(
     host: QSHost,
+    uiEventLogger: QsEventLogger,
     @Background backgroundLooper: Looper,
     @Main mainHandler: Handler,
     falsingManager: FalsingManager,
@@ -56,14 +58,15 @@
     qsLogger: QSLogger,
     private val controlsComponent: ControlsComponent
 ) : QSTileImpl<QSTile.State>(
-        host,
-        backgroundLooper,
-        mainHandler,
-        falsingManager,
-        metricsLogger,
-        statusBarStateController,
-        activityStarter,
-        qsLogger
+    host,
+    uiEventLogger,
+    backgroundLooper,
+    mainHandler,
+    falsingManager,
+    metricsLogger,
+    statusBarStateController,
+    activityStarter,
+    qsLogger
 ) {
 
     private var hasControlsApps = AtomicBoolean(false)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 434fe45..3e7bdd1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -54,6 +54,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -88,6 +89,7 @@
     @Inject
     public DndTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -100,7 +102,7 @@
             SecureSettings secureSettings,
             DialogLaunchAnimator dialogLaunchAnimator
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = zenModeController;
         mSharedPreferences = sharedPreferences;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index f913326..eef4c1d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -48,6 +48,7 @@
 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.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -90,6 +91,7 @@
     @Inject
     public DreamTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -105,7 +107,7 @@
             @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_DOCK_USER)
                     boolean dreamOnlyEnabledForDockUser
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mDreamManager = dreamManager;
         mBroadcastDispatcher = broadcastDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index e091a75..2c986da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 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.statusbar.policy.FlashlightController;
@@ -55,6 +56,7 @@
     @Inject
     public FlashlightTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -64,7 +66,7 @@
             QSLogger qsLogger,
             FlashlightController flashlightController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mFlashlightController = flashlightController;
         mFlashlightController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index 3f514344..12d9847 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -36,6 +36,7 @@
 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.statusbar.phone.SystemUIDialog
@@ -47,6 +48,7 @@
 @Inject
 constructor(
     host: QSHost,
+    uiEventLogger: QsEventLogger,
     @Background backgroundLooper: Looper,
     @Main mainHandler: Handler,
     falsingManager: FalsingManager,
@@ -61,6 +63,7 @@
 ) :
     QSTileImpl<QSTile.State?>(
         host,
+        uiEventLogger,
         backgroundLooper,
         mainHandler,
         falsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 6bf8b76..4c3699c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -41,6 +41,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 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.statusbar.policy.DataSaverController;
@@ -61,6 +62,7 @@
     @Inject
     public HotspotTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -71,7 +73,7 @@
             HotspotController hotspotController,
             DataSaverController dataSaverController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mHotspotController = hotspotController;
         mDataSaverController = dataSaverController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 75d0172..f16f0dc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -51,6 +51,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.AlphaControlledSignalTileView;
 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.qs.tiles.dialog.InternetDialogFactory;
@@ -90,6 +91,7 @@
     @Inject
     public InternetTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -101,7 +103,7 @@
             AccessPointController accessPointController,
             InternetDialogFactory internetDialogFactory
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mInternetDialogFactory = internetDialogFactory;
         mHandler = mainHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 27f5826..83c5688 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 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.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -59,6 +60,7 @@
     @Inject
     public LocationTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -70,7 +72,7 @@
             KeyguardStateController keyguardStateController,
             PanelInteractor panelInteractor
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = locationController;
         mKeyguard = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
index 2e475d4..86a6a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.FalsingManager;
 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.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -48,7 +49,9 @@
     public static final String TILE_SPEC = "mictoggle";
 
     @Inject
-    protected MicrophoneToggleTile(QSHost host,
+    protected MicrophoneToggleTile(
+            QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             MetricsLogger metricsLogger,
@@ -58,7 +61,7 @@
             QSLogger qsLogger,
             IndividualSensorPrivacyController sensorPrivacyController,
             KeyguardStateController keyguardStateController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger, sensorPrivacyController,
                 keyguardStateController);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index e189f80..29ccb76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -43,6 +43,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 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;
 
@@ -65,6 +66,7 @@
     @Inject
     public NfcTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -74,7 +76,7 @@
             QSLogger qsLogger,
             BroadcastDispatcher broadcastDispatcher
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBroadcastDispatcher = broadcastDispatcher;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index aacd53b..405e139 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -45,6 +45,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 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.statusbar.policy.LocationController;
@@ -80,6 +81,7 @@
     @Inject
     public NightDisplayTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -91,7 +93,7 @@
             ColorDisplayManager colorDisplayManager,
             NightDisplayListenerModule.Builder nightDisplayListenerBuilder
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mLocationController = locationController;
         mManager = colorDisplayManager;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
index ae67d99..1eb317a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
@@ -36,6 +36,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -57,6 +58,7 @@
     @Inject
     public OneHandedModeTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -66,7 +68,7 @@
             QSLogger qsLogger,
             UserTracker userTracker,
             SecureSettings secureSettings) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mSetting = new SettingObserver(secureSettings, mHandler,
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, userTracker.getUserId()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index 92f5272..9e365d3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
 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;
 
@@ -62,6 +63,7 @@
     @Inject
     public QRCodeScannerTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -70,7 +72,7 @@
             ActivityStarter activityStarter,
             QSLogger qsLogger,
             QRCodeScannerController qrCodeScannerController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mQRCodeScannerController = qrCodeScannerController;
         mQRCodeScannerController.observe(getLifecycle(), mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 4a3c563..e026bdb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -49,6 +49,7 @@
 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.statusbar.policy.KeyguardStateController;
@@ -83,6 +84,7 @@
     @Inject
     public QuickAccessWalletTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -94,7 +96,7 @@
             PackageManager packageManager,
             SecureSettings secureSettings,
             QuickAccessWalletController quickAccessWalletController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = quickAccessWalletController;
         mKeyguardStateController = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
index 10f1ce4..2e04afb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
@@ -38,6 +38,7 @@
 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.ReduceBrightColorsController;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -59,6 +60,7 @@
             @Named(RBC_AVAILABLE) boolean isAvailable,
             ReduceBrightColorsController reduceBrightColorsController,
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -67,7 +69,7 @@
             ActivityStarter activityStarter,
             QSLogger qsLogger
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mReduceBrightColorsController = reduceBrightColorsController;
         mReduceBrightColorsController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 8888c73..7f7f8ad6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -44,6 +44,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -71,6 +72,7 @@
     @Inject
     public RotationLockTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -83,7 +85,7 @@
             BatteryController batteryController,
             SecureSettings secureSettings
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = rotationLockController;
         mController.observe(this, mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 65592a7..2d4652d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -41,6 +41,7 @@
 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.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -74,6 +75,7 @@
     @Inject
     public ScreenRecordTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -88,7 +90,7 @@
             DialogLaunchAnimator dialogLaunchAnimator,
             PanelInteractor panelInteractor
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = controller;
         mController.observe(this, mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index d99c1d1..7c4f097 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -39,6 +39,7 @@
 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.statusbar.policy.IndividualSensorPrivacyController;
@@ -68,7 +69,9 @@
      */
     public abstract String getRestriction();
 
-    protected SensorPrivacyToggleTile(QSHost host,
+    protected SensorPrivacyToggleTile(
+            QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -78,7 +81,7 @@
             QSLogger qsLogger,
             IndividualSensorPrivacyController sensorPrivacyController,
             KeyguardStateController keyguardStateController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mSensorPrivacyController = sensorPrivacyController;
         mKeyguard = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index 809689c..a60d1ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -39,6 +39,7 @@
 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.statusbar.policy.BatteryController;
@@ -69,6 +70,7 @@
     @Inject
     public UiModeNightTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -80,7 +82,7 @@
             BatteryController batteryController,
             LocationController locationController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBatteryController = batteryController;
         mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 6a5c990..17e72e5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -40,6 +40,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 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.statusbar.phone.ManagedProfileController;
@@ -59,6 +60,7 @@
     @Inject
     public WorkModeTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -68,7 +70,7 @@
             QSLogger qsLogger,
             ManagedProfileController managedProfileController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mProfileController = managedProfileController;
         mProfileController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index aedd976..222a0f4f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3333,7 +3333,10 @@
 
         mGestureRecorder = recorder;
         mHideExpandedRunnable = hideExpandedRunnable;
-        mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
+        if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+            mNotificationStackScrollLayoutController.setShelfController(
+                    notificationShelfController);
+        }
         mNotificationShelfController = notificationShelfController;
         mLockscreenShadeTransitionController.bindController(notificationShelfController);
         updateMaxDisplayedNotifications(true);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 7cb1cbe..ef14d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -64,6 +64,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
@@ -132,6 +133,7 @@
     private final FalsingCollector mFalsingCollector;
     private final LockscreenGestureLogger mLockscreenGestureLogger;
     private final ShadeLogger mShadeLog;
+    private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     private final FeatureFlags mFeatureFlags;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final FalsingManager mFalsingManager;
@@ -318,7 +320,8 @@
             MetricsLogger metricsLogger,
             FeatureFlags featureFlags,
             InteractionJankMonitor interactionJankMonitor,
-            ShadeLogger shadeLog
+            ShadeLogger shadeLog,
+            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor
     ) {
         mPanelViewControllerLazy = panelViewControllerLazy;
         mPanelView = panelView;
@@ -357,6 +360,7 @@
         mLockscreenGestureLogger = lockscreenGestureLogger;
         mMetricsLogger = metricsLogger;
         mShadeLog = shadeLog;
+        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
         mFeatureFlags = featureFlags;
         mInteractionJankMonitor = interactionJankMonitor;
 
@@ -937,6 +941,7 @@
         // When expanding QS, let's authenticate the user if possible,
         // this will speed up notification actions.
         if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
+            mKeyguardFaceAuthInteractor.onQsExpansionStared();
             mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index c84894f..06f43f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -841,6 +841,19 @@
         BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
         behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
         behavior.setSkipCollapsed(true);
+        behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
+                    @Override
+                    public void onStateChanged(@NonNull View bottomSheet, int newState) {
+                        if (newState == BottomSheetBehavior.STATE_DRAGGING) {
+                            behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
+                        }
+                    }
+
+                    @Override
+                    public void onSlide(@NonNull View bottomSheet, float slideOffset) {
+                        // Do nothing.
+                    }
+                });
 
         mKeyboardShortcutsBottomSheetDialog.setCanceledOnTouchOutside(true);
         Window keyboardShortcutsWindow = mKeyboardShortcutsBottomSheetDialog.getWindow();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 9b1e2fa..142689e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -1423,7 +1423,7 @@
 
     private boolean canUnlockWithFingerprint() {
         return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser());
+                getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed();
     }
 
     private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index e6715a1..7eb63da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -24,6 +24,7 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.IndentingPrintWriter;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -52,6 +53,8 @@
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -96,6 +99,11 @@
     private NotificationShelfController mController;
     private float mActualWidth = -1;
     private boolean mSensitiveRevealAnimEndabled;
+    private boolean mShelfRefactorFlagEnabled;
+    private boolean mCanModifyColorOfNotifications;
+    private boolean mCanInteract;
+    private NotificationStackScrollLayout mHostLayout;
+    private NotificationRoundnessManager mRoundnessManager;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -132,6 +140,7 @@
 
     public void bind(AmbientState ambientState,
                      NotificationStackScrollLayoutController hostLayoutController) {
+        assertRefactorFlagDisabled();
         mAmbientState = ambientState;
         mHostLayoutController = hostLayoutController;
         hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
@@ -139,6 +148,14 @@
         });
     }
 
+    public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout,
+            NotificationRoundnessManager roundnessManager) {
+        if (!checkRefactorFlagEnabled()) return;
+        mAmbientState = ambientState;
+        mHostLayout = hostLayout;
+        mRoundnessManager = roundnessManager;
+    }
+
     private void updateResources() {
         Resources res = getResources();
         mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
@@ -230,7 +247,7 @@
             } else {
                 viewState.setAlpha(1f - ambientState.getHideAmount());
             }
-            viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
+            viewState.belowSpeedBump = getSpeedBumpIndex() == 0;
             viewState.hideSensitive = false;
             viewState.setXTranslation(getTranslationX());
             viewState.hasItemsInStableShelf = lastViewState.inShelf;
@@ -273,6 +290,14 @@
         }
     }
 
+    private int getSpeedBumpIndex() {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.getSpeedBumpIndex();
+        } else {
+            return mHostLayoutController.getSpeedBumpIndex();
+        }
+    }
+
     /**
      * @param fractionToShade Fraction of lockscreen to shade transition
      * @param shortestWidth   Shortest width to use for lockscreen shelf
@@ -385,8 +410,8 @@
         int baseZHeight = mAmbientState.getBaseZHeight();
         int clipTopAmount = 0;
 
-        for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
-            ExpandableView child = mHostLayoutController.getChildAt(i);
+        for (int i = 0; i < getHostLayoutChildCount(); i++) {
+            ExpandableView child = getHostLayoutChildAt(i);
             if (!child.needsClippingToShelf() || child.getVisibility() == GONE) {
                 continue;
             }
@@ -425,7 +450,7 @@
                     transitionAmount = inShelfAmount;
                 }
                 // We don't want to modify the color if the notification is hun'd
-                if (isLastChild && mController.canModifyColorOfNotifications()) {
+                if (isLastChild && canModifyColorOfNotifications()) {
                     if (colorOfViewBeforeLast == NO_COLOR) {
                         colorOfViewBeforeLast = ownColorUntinted;
                     }
@@ -471,11 +496,11 @@
 
         // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
         setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
-        mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
+        mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex());
         mShelfIcons.calculateIconXTranslations();
         mShelfIcons.applyIconStates();
-        for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
-            View child = mHostLayoutController.getChildAt(i);
+        for (int i = 0; i < getHostLayoutChildCount(); i++) {
+            View child = getHostLayoutChildAt(i);
             if (!(child instanceof ExpandableNotificationRow)
                     || child.getVisibility() == GONE) {
                 continue;
@@ -490,6 +515,30 @@
         }
     }
 
+    private ExpandableView getHostLayoutChildAt(int index) {
+        if (mShelfRefactorFlagEnabled) {
+            return (ExpandableView) mHostLayout.getChildAt(index);
+        } else {
+            return mHostLayoutController.getChildAt(index);
+        }
+    }
+
+    private int getHostLayoutChildCount() {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.getChildCount();
+        } else {
+            return mHostLayoutController.getChildCount();
+        }
+    }
+
+    private boolean canModifyColorOfNotifications() {
+        if (mShelfRefactorFlagEnabled) {
+            return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded();
+        } else {
+            return mController.canModifyColorOfNotifications();
+        }
+    }
+
     private void updateCornerRoundnessOnScroll(
             ActivatableNotificationView anv,
             float viewStart,
@@ -504,7 +553,7 @@
                 && anv == mAmbientState.getTrackedHeadsUpRow();
 
         final boolean shouldUpdateCornerRoundness = viewStart < shelfStart
-                && !mHostLayoutController.isViewAffectedBySwipe(anv)
+                && !isViewAffectedBySwipe(anv)
                 && !isUnlockedHeadsUp
                 && !isHunGoingToShade
                 && !anv.isAboveShelf()
@@ -556,6 +605,14 @@
         anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false);
     }
 
+    private boolean isViewAffectedBySwipe(ExpandableView expandableView) {
+        if (!mShelfRefactorFlagEnabled) {
+            return mHostLayoutController.isViewAffectedBySwipe(expandableView);
+        } else {
+            return mRoundnessManager.isViewAffectedBySwipe(expandableView);
+        }
+    }
+
     /**
      * Clips transient views to the top of the shelf - Transient views are only used for
      * disappearing views/animations and need to be clipped correctly by the shelf to ensure they
@@ -563,8 +620,8 @@
      * swipes quickly.
      */
     private void clipTransientViews() {
-        for (int i = 0; i < mHostLayoutController.getTransientViewCount(); i++) {
-            View transientView = mHostLayoutController.getTransientView(i);
+        for (int i = 0; i < getHostLayoutTransientViewCount(); i++) {
+            View transientView = getHostLayoutTransientView(i);
             if (transientView instanceof ExpandableView) {
                 ExpandableView transientExpandableView = (ExpandableView) transientView;
                 updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1);
@@ -572,6 +629,22 @@
         }
     }
 
+    private View getHostLayoutTransientView(int index) {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.getTransientView(index);
+        } else {
+            return mHostLayoutController.getTransientView(index);
+        }
+    }
+
+    private int getHostLayoutTransientViewCount() {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.getTransientViewCount();
+        } else {
+            return mHostLayoutController.getTransientViewCount();
+        }
+    }
+
     private void updateIconClipAmount(ExpandableNotificationRow row) {
         float maxTop = row.getTranslationY();
         if (getClipTopAmount() != 0) {
@@ -911,18 +984,27 @@
 
     @Override
     public void onStateChanged(int newState) {
+        assertRefactorFlagDisabled();
         mStatusBarState = newState;
         updateInteractiveness();
     }
 
     private void updateInteractiveness() {
-        mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf;
+        mInteractive = canInteract() && mHasItemsInStableShelf;
         setClickable(mInteractive);
         setFocusable(mInteractive);
         setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
                 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
     }
 
+    private boolean canInteract() {
+        if (mShelfRefactorFlagEnabled) {
+            return mCanInteract;
+        } else {
+            return mStatusBarState == StatusBarState.KEYGUARD;
+        }
+    }
+
     @Override
     protected boolean isInteractive() {
         return mInteractive;
@@ -959,12 +1041,46 @@
         return false;
     }
 
+    private void assertRefactorFlagDisabled() {
+        if (mShelfRefactorFlagEnabled) {
+            NotificationShelfController.throwIllegalFlagStateError(false);
+        }
+    }
+
+    private boolean checkRefactorFlagEnabled() {
+        if (!mShelfRefactorFlagEnabled) {
+            Log.wtf(TAG,
+                    "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled.");
+        }
+        return mShelfRefactorFlagEnabled;
+    }
+
     public void setController(NotificationShelfController notificationShelfController) {
+        assertRefactorFlagDisabled();
         mController = notificationShelfController;
     }
 
+    public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
+        if (!checkRefactorFlagEnabled()) return;
+        mCanModifyColorOfNotifications = canModifyColorOfNotifications;
+    }
+
+    public void setCanInteract(boolean canInteract) {
+        if (!checkRefactorFlagEnabled()) return;
+        mCanInteract = canInteract;
+        updateInteractiveness();
+    }
+
     public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
-        mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
+        mIndexOfFirstViewInShelf = getIndexOfViewInHostLayout(firstViewInShelf);
+    }
+
+    private int getIndexOfViewInHostLayout(ExpandableView child) {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.indexOfChild(child);
+        } else {
+            return mHostLayoutController.indexOfChild(child);
+        }
     }
 
     /**
@@ -975,6 +1091,15 @@
         mSensitiveRevealAnimEndabled = enabled;
     }
 
+    public void setRefactorFlagEnabled(boolean enabled) {
+        mShelfRefactorFlagEnabled = enabled;
+    }
+
+    public void requestRoundnessResetFor(ExpandableView child) {
+        if (!checkRefactorFlagEnabled()) return;
+        child.requestRoundnessReset(SHELF_SCROLL);
+    }
+
     /**
      * This method resets the OnScroll roundness of a view to 0f
      * <p>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
index bf3d47c..07cfd0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.statusbar
 
+import android.util.Log
 import android.view.View
 import android.view.View.OnClickListener
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView.OnActivatedListener
 import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -51,4 +54,29 @@
 
     /** @see View.setOnClickListener */
     fun setOnClickListener(listener: OnClickListener)
+
+    companion object {
+        @JvmStatic
+        fun assertRefactorFlagDisabled(featureFlags: FeatureFlags) {
+            if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+                throwIllegalFlagStateError(expected = false)
+            }
+        }
+
+        @JvmStatic
+        fun checkRefactorFlagEnabled(featureFlags: FeatureFlags): Boolean =
+            featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR).also { enabled ->
+                if (!enabled) {
+                    Log.wtf("NotifShelf", getErrorMessage(expected = true))
+                }
+            }
+
+        @JvmStatic
+        fun throwIllegalFlagStateError(expected: Boolean): Nothing =
+            error(getErrorMessage(expected))
+
+        private fun getErrorMessage(expected: Boolean): String =
+            "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is " +
+                if (expected) "disabled" else "enabled"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
index ecd0c41..fcff437 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -48,6 +48,10 @@
             View.SCALE_Y, R.id.scale_y_animator_tag, R.id.scale_y_animator_start_value_tag,
             R.id.scale_y_animator_end_value_tag);
 
+    public static final AnimatableProperty ALPHA = AnimatableProperty.from(
+            View.ALPHA, R.id.alpha_animator_tag, R.id.alpha_animator_start_value_tag,
+            R.id.alpha_animator_end_value_tag);
+
     /**
      * Similar to X, however this doesn't allow for any other modifications other than from this
      * property. When using X, it's possible that the view is laid out during the animation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a9d1255..950ab5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -299,6 +299,16 @@
      */
     private boolean mShowGroupBackgroundWhenExpanded;
 
+    /**
+     * True if we always show the collapsed layout on lockscreen because vertical space is low.
+     */
+    private boolean mSaveSpaceOnLockscreen;
+
+    /**
+     * True if we use intrinsic height regardless of vertical space available on lockscreen.
+     */
+    private boolean mIgnoreLockscreenConstraints;
+
     private OnClickListener mExpandClickListener = new OnClickListener() {
         @Override
         public void onClick(View v) {
@@ -394,6 +404,14 @@
         return mGroupExpansionChanging;
     }
 
+    public void setSaveSpaceOnLockscreen(boolean saveSpace) {
+        mSaveSpaceOnLockscreen = saveSpace;
+    }
+
+    public boolean getSaveSpaceOnLockscreen() {
+        return mSaveSpaceOnLockscreen;
+    }
+
     public void setGroupExpansionChanging(boolean changing) {
         mGroupExpansionChanging = changing;
     }
@@ -419,15 +437,9 @@
      */
     public void setAnimationRunning(boolean running) {
         // Sets animations running in the private/public layouts.
-        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE)) {
-            for (NotificationContentView l : mLayouts) {
-                if (l != null) {
-                    l.setContentAnimationRunning(running);
-                    setIconAnimationRunning(running, l);
-                }
-            }
-        } else {
-            for (NotificationContentView l : mLayouts) {
+        for (NotificationContentView l : mLayouts) {
+            if (l != null) {
+                l.setContentAnimationRunning(running);
                 setIconAnimationRunning(running, l);
             }
         }
@@ -2553,11 +2565,18 @@
     }
 
     @Override
+    public int getHeightWithoutLockscreenConstraints() {
+        mIgnoreLockscreenConstraints = true;
+        final int height = getIntrinsicHeight();
+        mIgnoreLockscreenConstraints = false;
+        return height;
+    }
+
+    @Override
     public int getIntrinsicHeight() {
         if (isUserLocked()) {
             return getActualHeight();
-        }
-        if (mGuts != null && mGuts.isExposed()) {
+        } else if (mGuts != null && mGuts.isExposed()) {
             return mGuts.getIntrinsicHeight();
         } else if ((isChildInGroup() && !isGroupExpanded())) {
             return mPrivateLayout.getMinHeight();
@@ -2579,13 +2598,14 @@
             return getCollapsedHeight();
         }
     }
-
     /**
      * @return {@code true} if the notification can show it's heads up layout. This is mostly true
      * except for legacy use cases.
      */
     public boolean canShowHeadsUp() {
-        if (mOnKeyguard && !isDozing() && !isBypassEnabled() && !mEntry.isStickyAndNotDemoted()) {
+        if (mOnKeyguard && !isDozing() && !isBypassEnabled() &&
+                (!mEntry.isStickyAndNotDemoted()
+                        || (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) {
             return false;
         }
         return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 9df6ba9..5edff5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -291,6 +291,11 @@
             long duration) {
     }
 
+    public int getHeightWithoutLockscreenConstraints() {
+        // ExpandableNotificationRow overrides this.
+        return getHeight();
+    }
+
     /**
      * @return The desired notification height.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
new file mode 100644
index 0000000..8ba65f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shelf.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Interactor for the [NotificationShelf] */
+@CentralSurfacesComponent.CentralSurfacesScope
+class NotificationShelfInteractor
+@Inject
+constructor(
+    private val keyguardRepository: KeyguardRepository,
+    private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
+) {
+    /** Is the shelf showing on the keyguard? */
+    val isShowingOnKeyguard: Flow<Boolean>
+        get() = keyguardRepository.isKeyguardShowing
+
+    /** Is the system in a state where the shelf is just a static display of notification icons? */
+    val isShelfStatic: Flow<Boolean>
+        get() =
+            combine(
+                keyguardRepository.isKeyguardShowing,
+                deviceEntryFaceAuthRepository.isBypassEnabled,
+            ) { isKeyguardShowing, isBypassEnabled ->
+                isKeyguardShowing && isBypassEnabled
+            }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index a235157..b190cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -14,37 +14,35 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.shelf.view
+package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
 
 import android.view.View
-import android.view.View.OnAttachStateChangeListener
 import android.view.accessibility.AccessibilityManager
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.NotificationShelfController
-import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController
 import com.android.systemui.statusbar.notification.row.ExpandableOutlineViewController
 import com.android.systemui.statusbar.notification.row.ExpandableViewController
+import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.NotificationIconContainer
 import com.android.systemui.statusbar.phone.NotificationTapHelper
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
-import dagger.Binds
-import dagger.Module
+import com.android.systemui.util.kotlin.getValue
+import dagger.Lazy
 import javax.inject.Inject
-
-/** Binds a [NotificationShelf] to its backend. */
-interface NotificationShelfViewBinder {
-    fun bind(shelf: NotificationShelf)
-}
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 
 /**
  * Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper
@@ -57,30 +55,30 @@
 @Inject
 constructor(
     private val shelf: NotificationShelf,
-    private val viewBinder: NotificationShelfViewBinder,
-    private val keyguardBypassController: KeyguardBypassController,
+    private val viewModel: NotificationShelfViewModel,
     featureFlags: FeatureFlags,
     private val notifTapHelperFactory: NotificationTapHelper.Factory,
     private val a11yManager: AccessibilityManager,
     private val falsingManager: FalsingManager,
     private val falsingCollector: FalsingCollector,
-    private val statusBarStateController: SysuiStatusBarStateController,
+    hostControllerLazy: Lazy<NotificationStackScrollLayoutController>,
 ) : NotificationShelfController {
 
-    private var ambientState: AmbientState? = null
+    private val hostController: NotificationStackScrollLayoutController by hostControllerLazy
 
     override val view: NotificationShelf
-        get() = shelf
+        get() = unsupported
 
     init {
         shelf.apply {
+            setRefactorFlagEnabled(featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR))
             useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES))
             setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
         }
     }
 
     fun init() {
-        viewBinder.bind(shelf)
+        NotificationShelfViewBinder.bind(viewModel, shelf)
 
         ActivatableNotificationViewController(
                 shelf,
@@ -91,23 +89,9 @@
                 falsingCollector,
             )
             .init()
-        shelf.setController(this)
-        val onAttachStateListener =
-            object : OnAttachStateChangeListener {
-                override fun onViewAttachedToWindow(v: View) {
-                    statusBarStateController.addCallback(
-                        shelf,
-                        SysuiStatusBarStateController.RANK_SHELF,
-                    )
-                }
-
-                override fun onViewDetachedFromWindow(v: View) {
-                    statusBarStateController.removeCallback(shelf)
-                }
-            }
-        shelf.addOnAttachStateChangeListener(onAttachStateListener)
-        if (shelf.isAttachedToWindow) {
-            onAttachStateListener.onViewAttachedToWindow(shelf)
+        hostController.setShelf(shelf)
+        hostController.setOnNotificationRemovedListener { child, _ ->
+            view.requestRoundnessResetFor(child)
         }
     }
 
@@ -117,10 +101,7 @@
     override val shelfIcons: NotificationIconContainer
         get() = shelf.shelfIcons
 
-    override fun canModifyColorOfNotifications(): Boolean {
-        return (ambientState?.isShadeExpanded == true &&
-            !(ambientState?.isOnKeyguard == true && keyguardBypassController.bypassEnabled))
-    }
+    override fun canModifyColorOfNotifications(): Boolean = unsupported
 
     override fun setOnActivatedListener(listener: ActivatableNotificationView.OnActivatedListener) {
         shelf.setOnActivatedListener(listener)
@@ -128,25 +109,27 @@
 
     override fun bind(
         ambientState: AmbientState,
-        notificationStackScrollLayoutController: NotificationStackScrollLayoutController
-    ) {
-        shelf.bind(ambientState, notificationStackScrollLayoutController)
-        this.ambientState = ambientState
-    }
+        notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+    ) = unsupported
 
     override fun setOnClickListener(listener: View.OnClickListener) {
         shelf.setOnClickListener(listener)
     }
+
+    private val unsupported: Nothing
+        get() = NotificationShelfController.throwIllegalFlagStateError(expected = true)
 }
 
-@Module(includes = [PrivateShelfViewBinderModule::class]) object NotificationShelfViewBinderModule
-
-@Module
-private interface PrivateShelfViewBinderModule {
-    @Binds fun bindImpl(impl: NotificationShelfViewBinderImpl): NotificationShelfViewBinder
-}
-
-@CentralSurfacesScope
-private class NotificationShelfViewBinderImpl @Inject constructor() : NotificationShelfViewBinder {
-    override fun bind(shelf: NotificationShelf) {}
+/** Binds a [NotificationShelf] to its backend. */
+object NotificationShelfViewBinder {
+    fun bind(viewModel: NotificationShelfViewModel, shelf: NotificationShelf) {
+        shelf.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.canModifyColorOfNotifications
+                    .onEach(shelf::setCanModifyColorOfNotifications)
+                    .launchIn(this)
+                viewModel.isClickable.onEach(shelf::setCanInteract).launchIn(this)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
new file mode 100644
index 0000000..5e297c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
+
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** ViewModel for [NotificationShelf]. */
+@CentralSurfacesScope
+class NotificationShelfViewModel
+@Inject
+constructor(
+    private val interactor: NotificationShelfInteractor,
+) {
+    /** Is the shelf allowed to be clickable when it has content? */
+    val isClickable: Flow<Boolean>
+        get() = interactor.isShowingOnKeyguard
+
+    /** Is the shelf allowed to modify the color of notifications in the host layout? */
+    val canModifyColorOfNotifications: Flow<Boolean>
+        get() = interactor.isShelfStatic.map { static -> !static }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
index 112d48c..00b9aa4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
@@ -32,6 +32,7 @@
     public long duration;
     public long delay;
     private ArrayMap<Property, Interpolator> mInterpolatorMap;
+    private Consumer<Property> mAnimationCancelAction;
     private Consumer<Property> mAnimationEndAction;
 
     /**
@@ -50,27 +51,43 @@
      * @return a listener that will be added for a given property during its animation.
      */
     public AnimatorListenerAdapter getAnimationFinishListener(Property property) {
-        if (mAnimationEndAction == null) {
+        if (mAnimationEndAction == null && mAnimationCancelAction == null) {
             return null;
         }
+        Consumer<Property> cancelAction = mAnimationCancelAction;
         Consumer<Property> endAction = mAnimationEndAction;
         return new AnimatorListenerAdapter() {
             private boolean mCancelled;
 
             @Override
             public void onAnimationCancel(Animator animation) {
-                 mCancelled = true;
+                mCancelled = true;
+                if (cancelAction != null) {
+                    cancelAction.accept(property);
+                }
             }
 
             @Override
             public void onAnimationEnd(Animator animation) {
-                if (!mCancelled) {
+                if (!mCancelled && endAction != null) {
                     endAction.accept(property);
                 }
             }
         };
     }
 
+    /**
+     * Add a callback for animation cancellation.
+     */
+    public AnimationProperties setAnimationCancelAction(Consumer<Property> listener) {
+        mAnimationCancelAction = listener;
+        return this;
+    }
+
+    /**
+     * Add a callback for animation ending successfully. The callback will not be called when the
+     * animations is cancelled.
+     */
     public AnimationProperties setAnimationEndAction(Consumer<Property> listener) {
         mAnimationEndAction = listener;
         return this;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e47e414..af608a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5137,8 +5137,26 @@
         requestChildrenUpdate();
     }
 
+    public void setShelf(NotificationShelf shelf) {
+        if (!NotificationShelfController.checkRefactorFlagEnabled(
+                mAmbientState.getFeatureFlags())) {
+            return;
+        }
+        int index = -1;
+        if (mShelf != null) {
+            index = indexOfChild(mShelf);
+            removeView(mShelf);
+        }
+        mShelf = shelf;
+        addView(mShelf, index);
+        mAmbientState.setShelf(mShelf);
+        mStateAnimator.setShelf(mShelf);
+        shelf.bind(mAmbientState, this, mController.getNotificationRoundnessManager());
+    }
+
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setShelfController(NotificationShelfController notificationShelfController) {
+        NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags());
         int index = -1;
         if (mShelf != null) {
             index = indexOfChild(mShelf);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 792746c..1c8727f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -73,6 +73,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -1382,6 +1383,7 @@
     }
 
     public void setShelfController(NotificationShelfController notificationShelfController) {
+        NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags);
         mView.setShelfController(notificationShelfController);
     }
 
@@ -1593,6 +1595,11 @@
         mView.setOnNotificationRemovedListener(listener);
     }
 
+    public void setShelf(NotificationShelf shelf) {
+        if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) return;
+        mView.setShelf(shelf);
+    }
+
     /**
      * Enum for UiEvent logged from this class
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 0922428..c7cb70c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -23,12 +23,14 @@
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState.KEYGUARD
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.util.Compile
+import com.android.systemui.util.LargeScreenUtils.shouldUseSplitNotificationShade
 import com.android.systemui.util.children
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -51,11 +53,10 @@
 constructor(
     private val statusBarStateController: SysuiStatusBarStateController,
     private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
+    private val mediaDataManager: MediaDataManager,
     @Main private val resources: Resources
 ) {
 
-    private lateinit var lastComputeHeightLog : String
-
     /**
      * Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf.
      * If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space
@@ -67,68 +68,158 @@
     /** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */
     private var dividerHeight by notNull<Float>()
 
+    /**
+     * True when there is not enough vertical space to show at least one notification with heads up
+     * layout. When true, notifications always show collapsed layout.
+     */
+    private var saveSpaceOnLockscreen = false
+
     init {
         updateResources()
     }
 
     /**
      * Returns whether notifications and (shelf if visible) can fit in total space available.
-     * [spaceForShelf] is extra vertical space allowed for the shelf to overlap the lock icon.
+     * [shelfSpace] is extra vertical space allowed for the shelf to overlap the lock icon.
      */
     private fun canStackFitInSpace(
         stackHeight: StackHeight,
-        spaceForNotifications: Float,
-        spaceForShelf: Float,
-    ): Boolean {
-
-        val (notificationsHeight, shelfHeightWithSpaceBefore) = stackHeight
-        var canFit: Boolean
+        notifSpace: Float,
+        shelfSpace: Float,
+    ): FitResult {
+        val (notifHeight, notifHeightSaveSpace, shelfHeightWithSpaceBefore) = stackHeight
 
         if (shelfHeightWithSpaceBefore == 0f) {
-            canFit = notificationsHeight <= spaceForNotifications
-            log {
-                "canStackFitInSpace[$canFit] = notificationsHeight[$notificationsHeight]" +
-                    " <= spaceForNotifications[$spaceForNotifications]"
+            if (notifHeight <= notifSpace) {
+                log {
+                    "\tcanStackFitInSpace[FIT] = notifHeight[$notifHeight]" +
+                        " <= notifSpace[$notifSpace]"
+                }
+                return FitResult.FIT
             }
-        } else {
-            canFit =
-                (notificationsHeight + shelfHeightWithSpaceBefore) <=
-                    (spaceForNotifications + spaceForShelf)
+            if (notifHeightSaveSpace <= notifSpace) {
+                log {
+                    "\tcanStackFitInSpace[FIT_IF_SAVE_SPACE]" +
+                        " = notifHeightSaveSpace[$notifHeightSaveSpace]" +
+                        " <= notifSpace[$notifSpace]"
+                }
+                return FitResult.FIT_IF_SAVE_SPACE
+            }
             log {
-                "canStackFitInSpace[$canFit] = (notificationsHeight[$notificationsHeight]" +
-                    " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
-                    " <= (spaceForNotifications[$spaceForNotifications] " +
-                    " + spaceForShelf[$spaceForShelf])"
+                "\tcanStackFitInSpace[NO_FIT]" +
+                    " = notifHeightSaveSpace[$notifHeightSaveSpace] > notifSpace[$notifSpace]"
+            }
+            return FitResult.NO_FIT
+        } else {
+            if ((notifHeight + shelfHeightWithSpaceBefore) <= (notifSpace + shelfSpace)) {
+                log {
+                    "\tcanStackFitInSpace[FIT] = (notifHeight[$notifHeight]" +
+                        " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+                        " <= (notifSpace[$notifSpace] " +
+                        " + spaceForShelf[$shelfSpace])"
+                }
+                return FitResult.FIT
+            } else if (
+                (notifHeightSaveSpace + shelfHeightWithSpaceBefore) <= (notifSpace + shelfSpace)
+            ) {
+                log {
+                    "\tcanStackFitInSpace[FIT_IF_SAVE_SPACE]" +
+                        " = (notifHeightSaveSpace[$notifHeightSaveSpace]" +
+                        " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+                        " <= (notifSpace[$notifSpace] + shelfSpace[$shelfSpace])"
+                }
+                return FitResult.FIT_IF_SAVE_SPACE
+            } else {
+                log {
+                    "\tcanStackFitInSpace[NO_FIT]" +
+                        " = (notifHeightSaveSpace[$notifHeightSaveSpace]" +
+                        " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+                        " > (notifSpace[$notifSpace] + shelfSpace[$shelfSpace])"
+                }
+                return FitResult.NO_FIT
             }
         }
-        return canFit
     }
 
     /**
-     * Given the [spaceForNotifications] and [spaceForShelf] constraints, calculate how many
-     * notifications to show. This number is only valid in keyguard.
+     * Given the [notifSpace] and [shelfSpace] constraints, calculate how many notifications to
+     * show. This number is only valid in keyguard.
      *
      * @param totalAvailableSpace space for notifications. This includes the space for the shelf.
      */
     fun computeMaxKeyguardNotifications(
         stack: NotificationStackScrollLayout,
-        spaceForNotifications: Float,
-        spaceForShelf: Float,
-        shelfIntrinsicHeight: Float
+        notifSpace: Float,
+        shelfSpace: Float,
+        shelfHeight: Float,
     ): Int {
+        log { "\n " }
+        log {
+            "computeMaxKeyguardNotifications ---" +
+                "\n\tnotifSpace $notifSpace" +
+                "\n\tspaceForShelf $shelfSpace" +
+                "\n\tshelfIntrinsicHeight $shelfHeight"
+        }
+        if (notifSpace + shelfSpace <= 0f) {
+            log { "--- No space to show anything. maxNotifs=0" }
+            return 0
+        }
         log { "\n" }
 
-        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
-            /* computeHeight= */ false)
+        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight)
+        val isMediaShowing = mediaDataManager.hasActiveMediaOrRecommendation()
 
-        var maxNotifications =
+        log { "\tGet maxNotifWithoutSavingSpace ---" }
+        val maxNotifWithoutSavingSpace =
             stackHeightSequence.lastIndexWhile { heightResult ->
                 canStackFitInSpace(
                     heightResult,
-                    spaceForNotifications = spaceForNotifications,
-                    spaceForShelf = spaceForShelf)
+                    notifSpace = notifSpace,
+                    shelfSpace = shelfSpace
+                ) == FitResult.FIT
             }
 
+        // How many notifications we can show at heightWithoutLockscreenConstraints
+        var minCountAtHeightWithoutConstraints =
+            if (isMediaShowing && !shouldUseSplitNotificationShade(resources)) 2 else 1
+        log {
+            "\t---maxNotifWithoutSavingSpace=$maxNotifWithoutSavingSpace " +
+                "isMediaShowing=$isMediaShowing" +
+                "minCountAtHeightWithoutConstraints=$minCountAtHeightWithoutConstraints"
+        }
+        log { "\n" }
+
+        var maxNotifications: Int
+        if (maxNotifWithoutSavingSpace >= minCountAtHeightWithoutConstraints) {
+            saveSpaceOnLockscreen = false
+            maxNotifications = maxNotifWithoutSavingSpace
+            log {
+                "\tDo NOT save space. maxNotifications=maxNotifWithoutSavingSpace=$maxNotifications"
+            }
+        } else {
+            log { "\tSAVE space ---" }
+            saveSpaceOnLockscreen = true
+            maxNotifications =
+                stackHeightSequence.lastIndexWhile { heightResult ->
+                    canStackFitInSpace(
+                        heightResult,
+                        notifSpace = notifSpace,
+                        shelfSpace = shelfSpace
+                    ) != FitResult.NO_FIT
+                }
+            log { "\t--- maxNotifications=$maxNotifications" }
+        }
+
+        // Must update views immediately to avoid mismatches between initial HUN layout height
+        // and the height adapted to lockscreen space constraints, which causes jump cuts.
+        stack.showableChildren().toList().forEach { currentNotification ->
+            run {
+                if (currentNotification is ExpandableNotificationRow) {
+                    currentNotification.saveSpaceOnLockscreen = saveSpaceOnLockscreen
+                }
+            }
+        }
+
         if (onLockscreen()) {
             maxNotifications = min(maxKeyguardNotifications, maxNotifications)
         }
@@ -137,53 +228,80 @@
         maxNotifications = max(0, maxNotifications)
         log {
             val sequence = if (SPEW) " stackHeightSequence=${stackHeightSequence.toList()}" else ""
-            "computeMaxKeyguardNotifications(" +
-                " spaceForNotifications=$spaceForNotifications" +
-                " spaceForShelf=$spaceForShelf" +
-                " shelfHeight=$shelfIntrinsicHeight) -> $maxNotifications$sequence"
+            "--- computeMaxKeyguardNotifications(" +
+                " notifSpace=$notifSpace" +
+                " shelfSpace=$shelfSpace" +
+                " shelfHeight=$shelfHeight) -> $maxNotifications$sequence"
         }
+        log { "\n" }
         return maxNotifications
     }
 
     /**
-     * Given the [maxNotifications] constraint, calculates the height of the
+     * Given the [maxNotifs] constraint, calculates the height of the
      * [NotificationStackScrollLayout]. This might or might not be in keyguard.
      *
      * @param stack stack containing notifications as children.
-     * @param maxNotifications Maximum number of notifications. When reached, the others will go
-     * into the shelf.
-     * @param shelfIntrinsicHeight height of the shelf, without any padding. It might be zero.
-     *
+     * @param maxNotifs Maximum number of notifications. When reached, the others will go into the
+     *   shelf.
+     * @param shelfHeight height of the shelf, without any padding. It might be zero.
      * @return height of the stack, including shelf height, if needed.
      */
     fun computeHeight(
         stack: NotificationStackScrollLayout,
-        maxNotifications: Int,
-        shelfIntrinsicHeight: Float
+        maxNotifs: Int,
+        shelfHeight: Float
     ): Float {
         log { "\n" }
-        lastComputeHeightLog = ""
-        val heightPerMaxNotifications =
-            computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
-                    /* computeHeight= */ true)
+        log { "computeHeight ---" }
 
-        val (notificationsHeight, shelfHeightWithSpaceBefore) =
-            heightPerMaxNotifications.elementAtOrElse(maxNotifications) {
-                heightPerMaxNotifications.last() // Height with all notifications visible.
+        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight)
+
+        val (notifsHeight, notifsHeightSavingSpace, shelfHeightWithSpaceBefore) =
+            stackHeightSequence.elementAtOrElse(maxNotifs) {
+                stackHeightSequence.last() // Height with all notifications visible.
             }
-        lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," +
-                "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " +
-                "${notificationsHeight + shelfHeightWithSpaceBefore}" +
-                " = ($notificationsHeight + $shelfHeightWithSpaceBefore)"
-        log {
-            lastComputeHeightLog
+
+        var height: Float
+        if (saveSpaceOnLockscreen) {
+            height = notifsHeightSavingSpace + shelfHeightWithSpaceBefore
+            log {
+                "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" +
+                    " -> $height=($notifsHeightSavingSpace+$shelfHeightWithSpaceBefore)," +
+                    " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen"
+            }
+        } else {
+            height = notifsHeight + shelfHeightWithSpaceBefore
+            log {
+                "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" +
+                    " -> ${height}=($notifsHeight+$shelfHeightWithSpaceBefore)" +
+                    " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen"
+            }
         }
-        return notificationsHeight + shelfHeightWithSpaceBefore
+        return height
     }
 
+    private enum class FitResult {
+        FIT,
+        FIT_IF_SAVE_SPACE,
+        NO_FIT
+    }
+
+    data class SpaceNeeded(
+        // Float height of spaceNeeded when showing heads up layout for FSI HUNs.
+        val whenEnoughSpace: Float,
+
+        // Float height of space needed when showing collapsed layout for FSI HUNs.
+        val whenSavingSpace: Float
+    )
+
     private data class StackHeight(
         // Float height with ith max notifications (not including shelf)
-        val notificationsHeight: Float,
+        val notifsHeight: Float,
+
+        // Float height with ith max notifications
+        // (not including shelf, using collapsed layout for FSI HUN)
+        val notifsHeightSavingSpace: Float,
 
         // Float height of shelf (0 if shelf is not showing), and space before the shelf that
         // changes during the lockscreen <=> full shade transition.
@@ -193,20 +311,27 @@
     private fun computeHeightPerNotificationLimit(
         stack: NotificationStackScrollLayout,
         shelfHeight: Float,
-        computeHeight: Boolean
     ): Sequence<StackHeight> = sequence {
-        log { "computeHeightPerNotificationLimit" }
-
         val children = stack.showableChildren().toList()
         var notifications = 0f
+        var notifsWithCollapsedHun = 0f
         var previous: ExpandableView? = null
         val onLockscreen = onLockscreen()
 
         // Only shelf. This should never happen, since we allow 1 view minimum (EmptyViewState).
-        yield(StackHeight(notificationsHeight = 0f, shelfHeightWithSpaceBefore = shelfHeight))
+        yield(
+            StackHeight(
+                notifsHeight = 0f,
+                notifsHeightSavingSpace = 0f,
+                shelfHeightWithSpaceBefore = shelfHeight
+            )
+        )
 
         children.forEachIndexed { i, currentNotification ->
-            notifications += spaceNeeded(currentNotification, i, previous, stack, onLockscreen)
+            val space = getSpaceNeeded(currentNotification, i, previous, stack, onLockscreen)
+            notifications += space.whenEnoughSpace
+            notifsWithCollapsedHun += space.whenSavingSpace
+
             previous = currentNotification
 
             val shelfWithSpaceBefore =
@@ -219,22 +344,23 @@
                             stack,
                             previous = currentNotification,
                             current = children[firstViewInShelfIndex],
-                            currentIndex = firstViewInShelfIndex)
+                            currentIndex = firstViewInShelfIndex
+                        )
                     spaceBeforeShelf + shelfHeight
                 }
 
-            val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " +
-                "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
-            if (computeHeight) {
-                lastComputeHeightLog += "\n" + currentLog
-            }
             log {
-                currentLog
+                "\tcomputeHeightPerNotificationLimit i=$i notifs=$notifications " +
+                    "notifsHeightSavingSpace=$notifsWithCollapsedHun" +
+                    " shelfWithSpaceBefore=$shelfWithSpaceBefore"
             }
             yield(
                 StackHeight(
-                    notificationsHeight = notifications,
-                    shelfHeightWithSpaceBefore = shelfWithSpaceBefore))
+                    notifsHeight = notifications,
+                    notifsHeightSavingSpace = notifsWithCollapsedHun,
+                    shelfHeightWithSpaceBefore = shelfWithSpaceBefore
+                )
+            )
         }
     }
 
@@ -256,32 +382,46 @@
     }
 
     @VisibleForTesting
-    fun spaceNeeded(
+    fun getSpaceNeeded(
         view: ExpandableView,
         visibleIndex: Int,
         previousView: ExpandableView?,
         stack: NotificationStackScrollLayout,
-        onLockscreen: Boolean
-    ): Float {
+        onLockscreen: Boolean,
+    ): SpaceNeeded {
         assert(view.isShowable(onLockscreen))
 
+        // Must use heightWithoutLockscreenConstraints because intrinsicHeight references
+        // mSaveSpaceOnLockscreen and using intrinsicHeight here will result in stack overflow.
+        val height = view.heightWithoutLockscreenConstraints.toFloat()
+        val gapAndDividerHeight =
+            calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex)
+
         var size =
             if (onLockscreen) {
                 if (view is ExpandableNotificationRow && view.entry.isStickyAndNotDemoted) {
-                    view.intrinsicHeight.toFloat()
+                    height
                 } else {
                     view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat()
                 }
             } else {
-                view.intrinsicHeight.toFloat()
+                height
             }
+        size += gapAndDividerHeight
 
-        size += calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex)
-        return size
+        var sizeWhenSavingSpace =
+            if (onLockscreen) {
+                view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat()
+            } else {
+                height
+            }
+        sizeWhenSavingSpace += gapAndDividerHeight
+
+        return SpaceNeeded(size, sizeWhenSavingSpace)
     }
 
     fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog")
+        pw.println("NotificationStackSizeCalculator saveSpaceOnLockscreen=$saveSpaceOnLockscreen")
     }
 
     private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 8ee2c6f..74ab47f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.util.Assert
 import com.android.systemui.util.sensors.AsyncSensorManager
@@ -46,6 +47,7 @@
     private val statusBarStateController: StatusBarStateController,
     private val asyncSensorManager: AsyncSensorManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
     private val dumpManager: DumpManager
 ) : Dumpable, CoreStartable {
 
@@ -72,6 +74,7 @@
             // Not listening anymore since trigger events unregister themselves
             isListening = false
             updateListeningState()
+            keyguardFaceAuthInteractor.onDeviceLifted()
             keyguardUpdateMonitor.requestFaceAuth(
                 FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
deleted file mode 100644
index 076e5f1..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.WindowInsets;
-import android.widget.FrameLayout;
-
-/**
- * A view group which contains the preview of phone/camera and draws a black bar at the bottom as
- * the fake navigation bar.
- */
-public class KeyguardPreviewContainer extends FrameLayout {
-
-    private Drawable mBlackBarDrawable = new Drawable() {
-        @Override
-        public void draw(Canvas canvas) {
-            canvas.save();
-            canvas.clipRect(0, getHeight() - getPaddingBottom(), getWidth(), getHeight());
-            canvas.drawColor(Color.BLACK);
-            canvas.restore();
-        }
-
-        @Override
-        public void setAlpha(int alpha) {
-            // noop
-        }
-
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) {
-            // noop
-        }
-
-        @Override
-        public int getOpacity() {
-            return android.graphics.PixelFormat.OPAQUE;
-        }
-    };
-
-    public KeyguardPreviewContainer(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setBackground(mBlackBarDrawable);
-    }
-
-    @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        setPadding(0, 0, 0, insets.getStableInsetBottom());
-        return super.onApplyWindowInsets(insets);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 49b58df..d496cfa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -69,6 +69,7 @@
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -285,8 +286,11 @@
     private final boolean mUdfpsNewTouchDetectionEnabled;
     private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
 
-    private OnDismissAction mAfterKeyguardGoneAction;
-    private Runnable mKeyguardGoneCancelAction;
+    @VisibleForTesting
+    OnDismissAction mAfterKeyguardGoneAction;
+
+    @VisibleForTesting
+    Runnable mKeyguardGoneCancelAction;
     private boolean mDismissActionWillAnimateOnKeyguard;
     private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
 
@@ -302,6 +306,8 @@
     @Nullable private KeyguardBypassController mBypassController;
     @Nullable private OccludingAppBiometricUI mOccludingAppBiometricUI;
 
+    private UserTracker mUserTracker;
+
     @Nullable private TaskbarDelegate mTaskbarDelegate;
     private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -339,7 +345,8 @@
             PrimaryBouncerInteractor primaryBouncerInteractor,
             BouncerView primaryBouncerView,
             AlternateBouncerInteractor alternateBouncerInteractor,
-            UdfpsOverlayInteractor udfpsOverlayInteractor
+            UdfpsOverlayInteractor udfpsOverlayInteractor,
+            UserTracker userTracker
     ) {
         mContext = context;
         mViewMediatorCallback = callback;
@@ -367,6 +374,7 @@
                 featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM);
         mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
         mUdfpsOverlayInteractor = udfpsOverlayInteractor;
+        mUserTracker = userTracker;
     }
 
     @Override
@@ -662,14 +670,21 @@
                 if (afterKeyguardGone) {
                     // we'll handle the dismiss action after keyguard is gone, so just show the
                     // bouncer
-                    mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
+                    if (mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
+                        mPrimaryBouncerInteractor.show(true);
+                    }
                 } else {
-                    // after authentication success, run dismiss action with the option to defer
-                    // hiding the keyguard based on the return value of the OnDismissAction
-                    mPrimaryBouncerInteractor.setDismissAction(
-                            mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
-                    mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
-                    // bouncer will handle the dismiss action, so we no longer need to track it here
+                    if (mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
+                        // after authentication success, run dismiss action with the option to defer
+                        // hiding the keyguard based on the return value of the OnDismissAction
+                        mPrimaryBouncerInteractor.setDismissAction(
+                                mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
+                        mPrimaryBouncerInteractor.show(true);
+                    } else {
+                        if (mAfterKeyguardGoneAction != null) {
+                            mAfterKeyguardGoneAction.onDismiss();
+                        }
+                    }
                     mAfterKeyguardGoneAction = null;
                     mKeyguardGoneCancelAction = null;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 118bfc5..0cd3401 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -175,15 +175,19 @@
         // We animate the Y properly separately using the PropertyAnimator, as the panel
         // view also needs to update the end position.
         PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y)
-        PropertyAnimator.setProperty<View>(keyguardView, AnimatableProperty.Y, currentY,
+        PropertyAnimator.setProperty(keyguardView, AnimatableProperty.Y, currentY,
                 AnimationProperties().setDuration(duration.toLong()),
                 true /* animate */)
 
-        keyguardView.animate()
+        // Cancel any existing CUJs before starting the animation
+        interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+
+        PropertyAnimator.setProperty(
+            keyguardView, AnimatableProperty.ALPHA, 1f,
+            AnimationProperties()
+                .setDelay(0)
                 .setDuration(duration.toLong())
-                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                .alpha(1f)
-                .withEndAction {
+                .setAnimationEndAction {
                     aodUiAnimationPlaying = false
 
                     // Lock the keyguard if it was waiting for the screen off animation to end.
@@ -199,30 +203,23 @@
                     // Done going to sleep, reset this flag.
                     decidedToAnimateGoingToSleep = null
 
-                    // We need to unset the listener. These are persistent for future animators
-                    keyguardView.animate().setListener(null)
                     interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
                 }
-                .setListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationCancel(animation: Animator?) {
-                        // If we're cancelled, reset state flags/listeners. The end action above
-                        // will not be called, which is what we want since that will finish the
-                        // screen off animation and show the lockscreen, which we don't want if we
-                        // were cancelled.
-                        aodUiAnimationPlaying = false
-                        decidedToAnimateGoingToSleep = null
-                        keyguardView.animate().setListener(null)
-
-                        interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
-                    }
-
-                    override fun onAnimationStart(animation: Animator?) {
-                        interactionJankMonitor.begin(
-                                mCentralSurfaces.notificationShadeWindowView,
-                                CUJ_SCREEN_OFF_SHOW_AOD)
-                    }
-                })
-                .start()
+                .setAnimationCancelAction {
+                    // If we're cancelled, reset state flags/listeners. The end action above
+                    // will not be called, which is what we want since that will finish the
+                    // screen off animation and show the lockscreen, which we don't want if we
+                    // were cancelled.
+                    aodUiAnimationPlaying = false
+                    decidedToAnimateGoingToSleep = null
+                    interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+                }
+                .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_SLOW_IN),
+            true /* animate */)
+        interactionJankMonitor.begin(
+            mCentralSurfaces.notificationShadeWindowView,
+            CUJ_SCREEN_OFF_SHOW_AOD
+        )
     }
 
     override fun onStartedWakingUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index cc2a0ba..5d4adda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -52,8 +52,7 @@
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
-import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderModule;
-import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderWrapperControllerImpl;
+import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
@@ -87,8 +86,7 @@
 import dagger.Provides;
 import dagger.multibindings.IntoSet;
 
-@Module(subcomponents = StatusBarFragmentComponent.class,
-        includes = { NotificationShelfViewBinderModule.class })
+@Module(subcomponents = StatusBarFragmentComponent.class)
 public abstract class StatusBarViewModule {
 
     public static final String SHADE_HEADER = "large_screen_shade_header";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 075e6ec..a05ab84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -26,15 +26,7 @@
 import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 /**
@@ -46,39 +38,16 @@
  * the list of available mobile lines of service for which we want to show icons.
  */
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class MobileUiAdapter
 @Inject
 constructor(
-    interactor: MobileIconsInteractor,
     private val iconController: StatusBarIconController,
-    private val iconsViewModelFactory: MobileIconsViewModel.Factory,
+    val mobileIconsViewModel: MobileIconsViewModel,
     private val logger: MobileViewLogger,
     @Application private val scope: CoroutineScope,
     private val statusBarPipelineFlags: StatusBarPipelineFlags,
 ) : CoreStartable {
-    private val mobileSubIds: Flow<List<Int>> =
-        interactor.filteredSubscriptions.mapLatest { subscriptions ->
-            subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
-        }
-
-    /**
-     * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
-     * the subscriptionId of the relevant subscriptions. These act as a key into the layouts which
-     * house the mobile infos.
-     *
-     * NOTE: this should go away as the view presenter learns more about this data pipeline
-     */
-    private val mobileSubIdsState: StateFlow<List<Int>> =
-        mobileSubIds
-            .distinctUntilChanged()
-            .onEach { logger.logUiAdapterSubIdsUpdated(it) }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
-
-    /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */
-    val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState)
-
     private var isCollecting: Boolean = false
     private var lastValue: List<Int>? = null
 
@@ -90,7 +59,7 @@
         if (statusBarPipelineFlags.useNewMobileIcons()) {
             scope.launch {
                 isCollecting = true
-                mobileSubIds.collectLatest {
+                mobileIconsViewModel.subscriptionIdsFlow.collectLatest {
                     logger.logUiAdapterSubIdsSentToIconController(it)
                     lastValue = it
                     iconController.setNewMobileIconSubIds(it)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
index 90dff23..f2f9143 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
@@ -41,15 +41,6 @@
 
     private val collectionStatuses = mutableMapOf<String, Boolean>()
 
-    fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
-        buffer.log(
-            TAG,
-            LogLevel.INFO,
-            { str1 = subs.toString() },
-            { "Sub IDs in MobileUiAdapter updated internally: $str1" },
-        )
-    }
-
     fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
         buffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 2b90065..9af5e83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -29,18 +29,23 @@
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 /**
  * View model for describing the system's current mobile cellular connections. The result is a list
  * of [MobileIconViewModel]s which describe the individual icons and can be bound to
- * [ModernStatusBarMobileView]
+ * [ModernStatusBarMobileView].
  */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
 class MobileIconsViewModel
 @Inject
 constructor(
-    val subscriptionIdsFlow: StateFlow<List<Int>>,
     val logger: MobileViewLogger,
     private val verboseLogger: VerboseMobileViewLogger,
     private val interactor: MobileIconsInteractor,
@@ -51,6 +56,13 @@
 ) {
     @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
 
+    val subscriptionIdsFlow: StateFlow<List<Int>> =
+        interactor.filteredSubscriptions
+            .mapLatest { subscriptions ->
+                subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
     init {
         scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } }
     }
@@ -79,30 +91,4 @@
         val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) }
         subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
     }
-
-    @SysUISingleton
-    class Factory
-    @Inject
-    constructor(
-        private val logger: MobileViewLogger,
-        private val verboseLogger: VerboseMobileViewLogger,
-        private val interactor: MobileIconsInteractor,
-        private val airplaneModeInteractor: AirplaneModeInteractor,
-        private val constants: ConnectivityConstants,
-        @Application private val scope: CoroutineScope,
-        private val statusBarPipelineFlags: StatusBarPipelineFlags,
-    ) {
-        fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
-            return MobileIconsViewModel(
-                subscriptionIdsFlow,
-                logger,
-                verboseLogger,
-                interactor,
-                airplaneModeInteractor,
-                constants,
-                scope,
-                statusBarPipelineFlags,
-            )
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 654ba04..1e63b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -21,6 +21,8 @@
 import static android.os.BatteryManager.EXTRA_HEALTH;
 import static android.os.BatteryManager.EXTRA_PRESENT;
 
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+
 import android.annotation.WorkerThread;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -169,7 +171,8 @@
     @Override
     public void setPowerSaveMode(boolean powerSave, View view) {
         if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view));
-        BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true);
+        BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true,
+                SAVER_ENABLED_QS);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index e9f0dcb..928e011 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -61,6 +61,7 @@
  * Manages the user switcher on the Keyguard.
  */
 @KeyguardUserSwitcherScope
+@Deprecated
 public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> {
 
     private static final String TAG = "KeyguardUserSwitcherController";
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index a20a5b2..e819f94 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -31,6 +31,7 @@
 import android.view.accessibility.AccessibilityManager
 import android.widget.ImageView
 import android.widget.TextView
+import androidx.annotation.DimenRes
 import androidx.annotation.IdRes
 import androidx.annotation.VisibleForTesting
 import com.android.internal.widget.CachingIconView
@@ -180,8 +181,9 @@
 
         // Button
         val buttonView = currentView.requireViewById<TextView>(R.id.end_button)
-        if (newInfo.endItem is ChipbarEndItem.Button) {
-            TextViewBinder.bind(buttonView, newInfo.endItem.text)
+        val hasButton = newInfo.endItem is ChipbarEndItem.Button
+        if (hasButton) {
+            TextViewBinder.bind(buttonView, (newInfo.endItem as ChipbarEndItem.Button).text)
 
             val onClickListener =
                 View.OnClickListener { clickedView ->
@@ -196,6 +198,12 @@
             buttonView.visibility = View.GONE
         }
 
+        currentView
+            .getInnerView()
+            .setEndPadding(
+                if (hasButton) R.dimen.chipbar_outer_padding_half else R.dimen.chipbar_outer_padding
+            )
+
         // ---- Overall accessibility ----
         val iconDesc = newInfo.startIcon.icon.contentDescription
         val loadedIconDesc =
@@ -309,6 +317,15 @@
         viewUtil.setRectToViewWindowLocation(view, outRect)
     }
 
+    private fun View.setEndPadding(@DimenRes endPaddingDimen: Int) {
+        this.setPaddingRelative(
+            this.paddingStart,
+            this.paddingTop,
+            context.resources.getDimensionPixelSize(endPaddingDimen),
+            this.paddingBottom,
+        )
+    }
+
     private fun Boolean.visibleIfTrue(): Int {
         return if (this) {
             View.VISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index 6e58f22..52f2d11 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -18,7 +18,7 @@
 
 import android.os.VibrationEffect
 import android.view.View
-import androidx.annotation.ColorRes
+import androidx.annotation.AttrRes
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
@@ -49,7 +49,9 @@
     override val priority: ViewPriority,
 ) : TemporaryViewInfo() {
     companion object {
-        @ColorRes val DEFAULT_ICON_TINT = R.color.chipbar_text_and_icon_color
+        // LINT.IfChange
+        @AttrRes val DEFAULT_ICON_TINT = com.android.internal.R.attr.materialColorOnSecondaryFixed
+        // LINT.ThenChange(systemui/res/layout/chipbar.xml)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index 05e5666..29f16c7 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -272,10 +272,10 @@
 
     private static boolean showApplicationIcon(ApplicationInfo appInfo,
             PackageManager packageManager) {
-        if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP)) {
+        if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP | FLAG_SYSTEM)) {
             return packageManager.getLaunchIntentForPackage(appInfo.packageName) != null;
         }
-        return !hasFlag(appInfo.flags, FLAG_SYSTEM);
+        return true;
     }
 
     private static boolean hasFlag(int flags, int flag) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
new file mode 100644
index 0000000..c587f2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.util.kotlin
+
+import dagger.Lazy
+import kotlin.reflect.KProperty
+
+/**
+ * Extension operator that allows developers to use [dagger.Lazy] as a property delegate:
+ * ```kotlin
+ *    class MyClass @Inject constructor(
+ *      lazyDependency: dagger.Lazy<Foo>,
+ *    ) {
+ *      val dependency: Foo by lazyDependency
+ *    }
+ * ```
+ */
+operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index 209ea41..58cffa7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -58,6 +58,7 @@
 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.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -418,6 +419,7 @@
         @Inject
         public MemoryTile(
                 QSHost host,
+                QsEventLogger uiEventLogger,
                 @Background Looper backgroundLooper,
                 @Main Handler mainHandler,
                 FalsingManager falsingManager,
@@ -428,7 +430,7 @@
                 GarbageMonitor monitor,
                 PanelInteractor panelInteractor
         ) {
-            super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+            super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                     statusBarStateController, activityStarter, qsLogger);
             gm = monitor;
             mPanelInteractor = panelInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
index 35af444..e3ed2b4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
@@ -16,20 +16,34 @@
 
 package com.android.systemui.volume;
 
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+
 import android.annotation.StringRes;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
-import android.os.CountDownTimer;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.util.NotificationChannels;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
 
 /**
  * A class that implements the four Computed Sound Dose-related warnings defined in {@link AudioManager}:
@@ -53,34 +67,58 @@
  * communication between the native audio framework that implements the dose computation and the
  * audio service.
  */
-public abstract class CsdWarningDialog extends SystemUIDialog
+public class CsdWarningDialog extends SystemUIDialog
         implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
 
     private static final String TAG = Util.logTag(CsdWarningDialog.class);
 
     private static final int KEY_CONFIRM_ALLOWED_AFTER_MS = 1000; // milliseconds
     // time after which action is taken when the user hasn't ack'd or dismissed the dialog
-    private static final int NO_ACTION_TIMEOUT_MS = 5000;
+    public static final int NO_ACTION_TIMEOUT_MS = 5000;
 
     private final Context mContext;
     private final AudioManager mAudioManager;
     private final @AudioManager.CsdWarning int mCsdWarning;
     private final Object mTimerLock = new Object();
+
     /**
      * Timer to keep track of how long the user has before an action (here volume reduction) is
      * taken on their behalf.
      */
     @GuardedBy("mTimerLock")
-    private final CountDownTimer mNoUserActionTimer;
+    private Runnable mNoUserActionRunnable;
+    private Runnable mCancelScheduledNoUserActionRunnable = null;
+
+    private final DelayableExecutor mDelayableExecutor;
+    private NotificationManager mNotificationManager;
+    private Runnable mOnCleanup;
 
     private long mShowTime;
 
-    public CsdWarningDialog(@AudioManager.CsdWarning int csdWarning, Context context,
-            AudioManager audioManager) {
+    /**
+     * To inject dependencies and allow for easier testing
+     */
+    @AssistedFactory
+    public interface Factory {
+        /**
+         * Create a dialog object
+         */
+        CsdWarningDialog create(int csdWarning, Runnable onCleanup);
+    }
+
+    @AssistedInject
+    public CsdWarningDialog(@Assisted @AudioManager.CsdWarning int csdWarning, Context context,
+            AudioManager audioManager, NotificationManager notificationManager,
+            @Background DelayableExecutor delayableExecutor, @Assisted Runnable onCleanup) {
         super(context);
         mCsdWarning = csdWarning;
         mContext = context;
         mAudioManager = audioManager;
+        mNotificationManager = notificationManager;
+        mOnCleanup = onCleanup;
+
+        mDelayableExecutor = delayableExecutor;
+
         getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
         setShowForAllUsers(true);
         setMessage(mContext.getString(getStringForWarning(csdWarning)));
@@ -95,25 +133,24 @@
                 Context.RECEIVER_EXPORTED_UNAUDITED);
 
         if (csdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
-            mNoUserActionTimer = new CountDownTimer(NO_ACTION_TIMEOUT_MS, NO_ACTION_TIMEOUT_MS) {
-                @Override
-                public void onTick(long millisUntilFinished) { }
-
-                @Override
-                public void onFinish() {
-                    if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
-                        // unlike on the 5x dose repeat, level is only reduced to RS1
-                        // when the warning is not acknowledged quick enough
-                        mAudioManager.lowerVolumeToRs1();
-                    }
+            mNoUserActionRunnable = () -> {
+                if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
+                    // unlike on the 5x dose repeat, level is only reduced to RS1 when the warning
+                    // is not acknowledged quickly enough
+                    mAudioManager.lowerVolumeToRs1();
+                    sendNotification();
                 }
             };
         } else {
-            mNoUserActionTimer = null;
+            mNoUserActionRunnable = null;
         }
     }
 
-    protected abstract void cleanUp();
+    private void cleanUp() {
+        if (mOnCleanup != null) {
+            mOnCleanup.run();
+        }
+    }
 
     // NOT overriding onKeyDown as we're not allowing a dismissal on any key other than
     // VOLUME_DOWN, and for this, we don't need to track if it's the start of a new
@@ -153,12 +190,9 @@
         super.onStart();
         mShowTime = System.currentTimeMillis();
         synchronized (mTimerLock) {
-            if (mNoUserActionTimer != null) {
-                new Thread(() -> {
-                    synchronized (mTimerLock) {
-                        mNoUserActionTimer.start();
-                    }
-                }).start();
+            if (mNoUserActionRunnable != null) {
+                mCancelScheduledNoUserActionRunnable = mDelayableExecutor.executeDelayed(
+                        mNoUserActionRunnable, NO_ACTION_TIMEOUT_MS);
             }
         }
     }
@@ -166,8 +200,8 @@
     @Override
     protected void onStop() {
         synchronized (mTimerLock) {
-            if (mNoUserActionTimer != null) {
-                mNoUserActionTimer.cancel();
+            if (mCancelScheduledNoUserActionRunnable != null) {
+                mCancelScheduledNoUserActionRunnable.run();
             }
         }
     }
@@ -212,4 +246,32 @@
         Log.e(TAG, "Invalid CSD warning event " + csdWarning, new Exception());
         return com.android.internal.R.string.csd_dose_reached_warning;
     }
+
+
+    /**
+     * In case user did not respond to the dialog, they still need to know volume was lowered.
+     */
+    private void sendNotification() {
+        Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
+                FLAG_IMMUTABLE);
+
+        String text = mContext.getString(R.string.csd_system_lowered_text);
+        String title = mContext.getString(R.string.csd_lowered_title);
+
+        Notification.Builder builder =
+                new Notification.Builder(mContext, NotificationChannels.ALERTS)
+                        .setSmallIcon(R.drawable.hearing)
+                        .setContentTitle(title)
+                        .setContentText(text)
+                        .setContentIntent(pendingIntent)
+                        .setStyle(new Notification.BigTextStyle().bigText(text))
+                        .setVisibility(Notification.VISIBILITY_PUBLIC)
+                        .setLocalOnly(true)
+                        .setAutoCancel(true)
+                        .setCategory(Notification.CATEGORY_SYSTEM);
+
+        mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO,
+                builder.build());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 95cc12a..3c007f9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -263,6 +263,7 @@
     private final ConfigurationController mConfigurationController;
     private final MediaOutputDialogFactory mMediaOutputDialogFactory;
     private final VolumePanelFactory mVolumePanelFactory;
+    private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
     private final ActivityStarter mActivityStarter;
 
     private boolean mShowing;
@@ -311,6 +312,7 @@
             InteractionJankMonitor interactionJankMonitor,
             DeviceConfigProxy deviceConfigProxy,
             Executor executor,
+            CsdWarningDialog.Factory csdWarningDialogFactory,
             DumpManager dumpManager) {
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
@@ -322,6 +324,7 @@
         mConfigurationController = configurationController;
         mMediaOutputDialogFactory = mediaOutputDialogFactory;
         mVolumePanelFactory = volumePanelFactory;
+        mCsdWarningDialogFactory = csdWarningDialogFactory;
         mActivityStarter = activityStarter;
         mShowActiveStreamOnly = showActiveStreamOnly();
         mHasSeenODICaptionsTooltip =
@@ -2084,21 +2087,21 @@
         rescheduleTimeoutH();
     }
 
-    private void showCsdWarningH(int csdWarning, int durationMs) {
+    @VisibleForTesting void showCsdWarningH(int csdWarning, int durationMs) {
         synchronized (mSafetyWarningLock) {
+
             if (mCsdDialog != null) {
                 return;
             }
-            mCsdDialog = new CsdWarningDialog(csdWarning,
-                    mContext, mController.getAudioManager()) {
-                @Override
-                protected void cleanUp() {
-                    synchronized (mSafetyWarningLock) {
-                        mCsdDialog = null;
-                    }
-                    recheckH(null);
+
+            final Runnable cleanUp = () -> {
+                synchronized (mSafetyWarningLock) {
+                    mCsdDialog = null;
                 }
+                recheckH(null);
             };
+
+            mCsdDialog = mCsdWarningDialogFactory.create(csdWarning, cleanUp);
             mCsdDialog.show();
         }
         recheckH(null);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 0ab6c69..14d3ca3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -30,17 +30,18 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.volume.CsdWarningDialog;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
 import com.android.systemui.volume.VolumeDialogImpl;
 import com.android.systemui.volume.VolumePanelFactory;
 
-import java.util.concurrent.Executor;
-
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.concurrent.Executor;
+
 
 /** Dagger Module for code in the volume package. */
 @Module
@@ -63,6 +64,7 @@
             InteractionJankMonitor interactionJankMonitor,
             DeviceConfigProxy deviceConfigProxy,
             @Main Executor executor,
+            CsdWarningDialog.Factory csdFactory,
             DumpManager dumpManager) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
@@ -76,6 +78,7 @@
                 interactionJankMonitor,
                 deviceConfigProxy,
                 executor,
+                csdFactory,
                 dumpManager);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
deleted file mode 100644
index e93e862..0000000
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
+++ /dev/null
@@ -1,174 +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 androidx.core.animation;
-
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.AndroidRuntimeException;
-
-import androidx.annotation.NonNull;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * NOTE: this is a copy of the {@link androidx.core.animation.AnimatorTestRule} which attempts to
- * circumvent the problems with {@link androidx.core.animation.AnimationHandler} having a static
- * list of callbacks.
- *
- * TODO(b/275602127): remove this and use the original rule once we have the updated androidx code.
- */
-public final class AnimatorTestRule2 implements TestRule {
-
-    class TestAnimationHandler extends AnimationHandler {
-        TestAnimationHandler() {
-            super(new TestProvider());
-        }
-
-        List<AnimationFrameCallback> animationCallbacks = new ArrayList<>();
-
-        @Override
-        void addAnimationFrameCallback(AnimationFrameCallback callback) {
-            animationCallbacks.add(callback);
-            callback.doAnimationFrame(getCurrentTime());
-        }
-
-        @Override
-        public void removeCallback(AnimationFrameCallback callback) {
-            int id = animationCallbacks.indexOf(callback);
-            if (id >= 0) {
-                animationCallbacks.set(id, null);
-            }
-        }
-
-        void onAnimationFrame(long frameTime) {
-            for (int i = 0; i < animationCallbacks.size(); i++) {
-                final AnimationFrameCallback callback = animationCallbacks.get(i);
-                if (callback == null) {
-                    continue;
-                }
-                callback.doAnimationFrame(frameTime);
-            }
-        }
-
-        @Override
-        void autoCancelBasedOn(ObjectAnimator objectAnimator) {
-            for (int i = animationCallbacks.size() - 1; i >= 0; i--) {
-                AnimationFrameCallback cb = animationCallbacks.get(i);
-                if (cb == null) {
-                    continue;
-                }
-                if (objectAnimator.shouldAutoCancel(cb)) {
-                    ((Animator) animationCallbacks.get(i)).cancel();
-                }
-            }
-        }
-    }
-
-    final TestAnimationHandler mTestHandler;
-    final long mStartTime;
-    private long mTotalTimeDelta = 0;
-    private final Object mLock = new Object();
-
-    public AnimatorTestRule2() {
-        mStartTime = SystemClock.uptimeMillis();
-        mTestHandler = new TestAnimationHandler();
-    }
-
-    @NonNull
-    @Override
-    public Statement apply(@NonNull final Statement base, @NonNull Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                AnimationHandler.setTestHandler(mTestHandler);
-                try {
-                    base.evaluate();
-                } finally {
-                    AnimationHandler.setTestHandler(null);
-                }
-            }
-        };
-    }
-
-    /**
-     * Advances the animation clock by the given amount of delta in milliseconds. This call will
-     * produce an animation frame to all the ongoing animations. This method needs to be
-     * called on the same thread as {@link Animator#start()}.
-     *
-     * @param timeDelta the amount of milliseconds to advance
-     */
-    public void advanceTimeBy(long timeDelta) {
-        if (Looper.myLooper() == null) {
-            // Throw an exception
-            throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be"
-                    + "called on Looper threads");
-        }
-        synchronized (mLock) {
-            // Advance time & pulse a frame
-            mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta;
-        }
-        // produce a frame
-        mTestHandler.onAnimationFrame(getCurrentTime());
-    }
-
-
-    /**
-     * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a
-     * different time than the time tracked by {@link SystemClock} This method needs to be called on
-     * the same thread as {@link Animator#start()}.
-     */
-    public long getCurrentTime() {
-        if (Looper.myLooper() == null) {
-            // Throw an exception
-            throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be"
-                    + "called on Looper threads");
-        }
-        synchronized (mLock) {
-            return mStartTime + mTotalTimeDelta;
-        }
-    }
-
-
-    private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider {
-        TestProvider() {
-        }
-
-        @Override
-        public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) {
-            callback.doAnimationFrame(getCurrentTime());
-        }
-
-        @Override
-        public void postFrameCallback() {
-        }
-
-        @Override
-        public void setFrameDelay(long delay) {
-        }
-
-        @Override
-        public long getFrameDelay() {
-            return 0;
-        }
-    }
-}
-
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
index bddd60b..e7738af 100644
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
+++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
@@ -30,7 +30,7 @@
 @RunWithLooper(setAsMainLooper = true)
 class AnimatorTestRuleTest : SysuiTestCase() {
 
-    @get:Rule val animatorTestRule = AnimatorTestRule2()
+    @get:Rule val animatorTestRule = AnimatorTestRule()
 
     @Test
     fun testA() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index ecf7e0d..5557efa 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -38,8 +38,6 @@
 import static org.mockito.Mockito.when;
 
 import android.content.pm.PackageManager;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
 import android.provider.Settings;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
@@ -52,6 +50,8 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -88,8 +88,7 @@
     private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0,
             TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
             DATA_ROAMING_ENABLE, null, null, null, null, false, null, "");
-    @Mock
-    private WifiManager mWifiManager;
+    private FakeWifiRepository mWifiRepository = new FakeWifiRepository();
     @Mock
     private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
@@ -121,7 +120,6 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mContext.addMockSystemService(WifiManager.class, mWifiManager);
         mContext.addMockSystemService(PackageManager.class, mPackageManager);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
         mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
@@ -144,7 +142,7 @@
         when(mTelephonyManager.getActiveModemCount()).thenReturn(3);
 
         mCarrierTextManager = new CarrierTextManager.Builder(
-                mContext, mContext.getResources(), mWifiManager,
+                mContext, mContext.getResources(), mWifiRepository,
                 mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor,
                 mBgExecutor, mKeyguardUpdateMonitor)
                 .setShowAirplaneMode(true)
@@ -364,7 +362,11 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
                 TelephonyManager.SIM_STATE_READY);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
-        mockWifi();
+
+        assertFalse(mWifiRepository.isWifiConnectedWithValidSsid());
+        mWifiRepository.setWifiNetwork(
+                new WifiNetworkModel.Active(0, false, 0, "", false, false, null));
+        assertTrue(mWifiRepository.isWifiConnectedWithValidSsid());
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
         ServiceState ss = mock(ServiceState.class);
@@ -385,13 +387,6 @@
         assertNotEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
     }
 
-    private void mockWifi() {
-        when(mWifiManager.isWifiEnabled()).thenReturn(true);
-        WifiInfo wifiInfo = mock(WifiInfo.class);
-        when(wifiInfo.getBSSID()).thenReturn("");
-        when(mWifiManager.getConnectionInfo()).thenReturn(wifiInfo);
-    }
-
     @Test
     public void testCreateInfo_noSubscriptions() {
         reset(mCarrierTextCallback);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 2f627cb..b9f8dd94 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -48,8 +48,10 @@
 import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.ClockEvents;
+import com.android.systemui.plugins.ClockFaceConfig;
 import com.android.systemui.plugins.ClockFaceController;
 import com.android.systemui.plugins.ClockFaceEvents;
+import com.android.systemui.plugins.ClockTickRate;
 import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -185,6 +187,10 @@
         when(mClockController.getAnimations()).thenReturn(mClockAnimations);
         when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
         when(mClockEventController.getClock()).thenReturn(mClockController);
+        when(mSmallClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false));
+        when(mLargeClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false));
 
         mSliceView = new View(getContext());
         when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
@@ -367,6 +373,28 @@
     }
 
     @Test
+    public void testChangeClockDateWeatherEnabled_SetsDateWeatherViewVisibility() {
+        ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
+        when(mSmartspaceController.isEnabled()).thenReturn(true);
+        when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true);
+        when(mSmartspaceController.isWeatherEnabled()).thenReturn(true);
+        mController.init();
+        mExecutor.runAllReady();
+        assertEquals(View.VISIBLE, mFakeDateView.getVisibility());
+
+        when(mSmallClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true));
+        when(mLargeClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true));
+        verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
+        listenerArgumentCaptor.getValue().onCurrentClockChanged();
+
+        mExecutor.runAllReady();
+        assertEquals(View.GONE, mFakeDateView.getVisibility());
+    }
+
+    @Test
     public void testGetClock_nullClock_returnsNull() {
         when(mClockEventController.getClock()).thenReturn(null);
         assertNull(mController.getClock());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index d760189..3e4fd89 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -67,6 +67,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -208,7 +209,8 @@
                 mConfigurationController, mFalsingCollector, mFalsingManager,
                 mUserSwitcherController, mFeatureFlags, mGlobalSettings,
                 mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate,
-                mTelephonyManager, mViewMediatorCallback, mAudioManager);
+                mTelephonyManager, mViewMediatorCallback, mAudioManager,
+                mock(KeyguardFaceAuthInteractor.class));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index f1ee108..2c1d2ad 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,7 +16,9 @@
 
 package com.android.keyguard;
 
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -25,6 +27,9 @@
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.plugins.ClockConfig;
+import com.android.systemui.plugins.ClockController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -76,7 +81,16 @@
                 mScreenOffAnimationController,
                 mKeyguardLogger,
                 mFeatureFlags,
-                mInteractionJankMonitor);
+                mInteractionJankMonitor) {
+                    @Override
+                    void setProperty(
+                            AnimatableProperty property,
+                            float value,
+                            boolean animate) {
+                        // Route into the mock version for verification
+                        mControllerMock.setProperty(property, value, animate);
+                    }
+                };
     }
 
     @Test
@@ -111,4 +125,34 @@
         configurationListenerArgumentCaptor.getValue().onLocaleListChanged();
         verify(mKeyguardClockSwitchController).onLocaleListChanged();
     }
+
+    @Test
+    public void updatePosition_primaryClockAnimation() {
+        ClockController mockClock = mock(ClockController.class);
+        when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
+        when(mockClock.getConfig()).thenReturn(new ClockConfig(false, false, true));
+
+        mController.updatePosition(10, 15, 20f, true);
+
+        verify(mControllerMock).setProperty(AnimatableProperty.Y, 15f, true);
+        verify(mKeyguardClockSwitchController).updatePosition(
+                10, 20f, KeyguardStatusViewController.CLOCK_ANIMATION_PROPERTIES, true);
+        verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 1f, true);
+        verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 1f, true);
+    }
+
+    @Test
+    public void updatePosition_alternateClockAnimation() {
+        ClockController mockClock = mock(ClockController.class);
+        when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
+        when(mockClock.getConfig()).thenReturn(new ClockConfig(false, true, true));
+
+        mController.updatePosition(10, 15, 20f, true);
+
+        verify(mControllerMock).setProperty(AnimatableProperty.Y, 15f, true);
+        verify(mKeyguardClockSwitchController).updatePosition(
+                10, 1f, KeyguardStatusViewController.CLOCK_ANIMATION_PROPERTIES, true);
+        verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 20f, true);
+        verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 20f, true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
new file mode 100644
index 0000000..01d3a39
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
@@ -0,0 +1,119 @@
+/*
+ * 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
+
+import android.graphics.Point
+import android.hardware.display.DisplayManagerGlobal
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.decor.FaceScanningProviderFactory
+import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.log.ScreenDecorationsLogger
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FaceScanningProviderFactoryTest : SysuiTestCase() {
+
+    private lateinit var underTest: FaceScanningProviderFactory
+
+    @Mock private lateinit var authController: AuthController
+
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    @Mock private lateinit var display: Display
+
+    private val displayId = 2
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        val displayInfo = DisplayInfo()
+        val dmGlobal = mock(DisplayManagerGlobal::class.java)
+        val display =
+            Display(
+                dmGlobal,
+                displayId,
+                displayInfo,
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+            )
+        whenever(dmGlobal.getDisplayInfo(eq(displayId))).thenReturn(displayInfo)
+        val displayContext = context.createDisplayContext(display) as SysuiTestableContext
+        displayContext.orCreateTestableResources.addOverride(
+            R.array.config_displayUniqueIdArray,
+            arrayOf(displayId)
+        )
+        displayContext.orCreateTestableResources.addOverride(
+            R.bool.config_fillMainBuiltInDisplayCutout,
+            true
+        )
+        underTest =
+            FaceScanningProviderFactory(
+                authController,
+                displayContext,
+                statusBarStateController,
+                keyguardUpdateMonitor,
+                mock(Executor::class.java),
+                ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest"))
+            )
+
+        whenever(authController.faceSensorLocation).thenReturn(Point(10, 10))
+    }
+
+    @Test
+    fun shouldNotShowFaceScanningAnimationIfFaceIsNotEnrolled() {
+        whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
+        whenever(authController.isShowing).thenReturn(true)
+
+        assertThat(underTest.shouldShowFaceScanningAnim()).isFalse()
+    }
+
+    @Test
+    fun shouldShowFaceScanningAnimationIfBiometricPromptIsShowing() {
+        whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
+        whenever(authController.isShowing).thenReturn(true)
+
+        assertThat(underTest.shouldShowFaceScanningAnim()).isTrue()
+    }
+
+    @Test
+    fun shouldShowFaceScanningAnimationIfKeyguardFaceDetectionIsShowing() {
+        whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
+        whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true)
+
+        assertThat(underTest.shouldShowFaceScanningAnim()).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 0978c82..9c36af3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 import static android.view.Choreographer.FrameCallback;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
@@ -172,6 +173,12 @@
                 returnsSecondArg());
 
         mResources = getContext().getOrCreateTestableResources().getResources();
+        // prevent the config orientation from undefined, which may cause config.diff method
+        // neglecting the orientation update.
+        if (mResources.getConfiguration().orientation == ORIENTATION_UNDEFINED) {
+            mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT;
+        }
+
         mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
                 mContext, mValueAnimator);
         mWindowMagnificationController =
@@ -688,7 +695,11 @@
 
     @Test
     public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
-        final Configuration config = mContext.getResources().getConfiguration();
+        // the config orientation should not be undefined, since it would cause config.diff
+        // returning 0 and thus the orientation changed would not be detected
+        assertNotEquals(ORIENTATION_UNDEFINED, mResources.getConfiguration().orientation);
+
+        final Configuration config = mResources.getConfiguration();
         config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
                 : ORIENTATION_LANDSCAPE;
         final int newRotation = simulateRotateTheDevice();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
index 921f9a8..2b95973 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
@@ -37,7 +37,13 @@
 
     @Test
     fun onBackProgressed_shouldInvoke_onBackProgress() {
-        val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT)
+        val backEvent =
+            BackEvent(
+                /* touchX = */ 0f,
+                /* touchY = */ 0f,
+                /* progress = */ 0f,
+                /* swipeEdge = */ BackEvent.EDGE_LEFT
+            )
         onBackAnimationCallback.onBackStarted(backEvent)
 
         onBackAnimationCallback.onBackProgressed(backEvent)
@@ -47,7 +53,13 @@
 
     @Test
     fun onBackStarted_shouldInvoke_onBackStart() {
-        val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT)
+        val backEvent =
+            BackEvent(
+                /* touchX = */ 0f,
+                /* touchY = */ 0f,
+                /* progress = */ 0f,
+                /* swipeEdge = */ BackEvent.EDGE_LEFT
+            )
 
         onBackAnimationCallback.onBackStarted(backEvent)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index b41053c..ef750be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -51,32 +51,37 @@
     @Test
     fun testEnableDetector_expandWithTrack_shouldPostRunnable() {
         detector.enable(action)
-        // simulate notification expand
-        shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
+        shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f)
         verify(action).run()
     }
 
     @Test
     fun testEnableDetector_trackOnly_shouldPostRunnable() {
         detector.enable(action)
-        // simulate notification expand
-        shadeExpansionStateManager.onPanelExpansionChanged(5566f, false, true, 5566f)
+        shadeExpansionStateManager.onPanelExpansionChanged(1.0f, false, true, 0f)
         verify(action).run()
     }
 
     @Test
     fun testEnableDetector_expandOnly_shouldPostRunnable() {
         detector.enable(action)
-        // simulate notification expand
-        shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, false, 5566f)
+        shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, false, 0f)
         verify(action).run()
     }
 
     @Test
+    fun testEnableDetector_expandWithoutFraction_shouldPostRunnable() {
+        detector.enable(action)
+        // simulate headsup notification
+        shadeExpansionStateManager.onPanelExpansionChanged(0.0f, true, false, 0f)
+        verifyZeroInteractions(action)
+    }
+
+    @Test
     fun testEnableDetector_shouldNotPostRunnable() {
         detector.enable(action)
         detector.disable()
-        shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
+        shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f)
         verifyZeroInteractions(action)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 64c028e..eae95a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -87,6 +87,7 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
@@ -311,7 +312,8 @@
                 mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
                 mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
                 mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
-                mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils);
+                mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils,
+                mock(KeyguardFaceAuthInteractor.class));
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
index 08427da..21397d97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
@@ -269,6 +269,30 @@
     }
 
     @Test
+    public void testInputEventPropagationAfterRemoval() {
+        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+
+        final Environment environment = new Environment(Stream.of(touchHandler)
+                .collect(Collectors.toCollection(HashSet::new)));
+
+        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+        environment.publishInputEvent(initialEvent);
+
+        // Ensure session started
+        final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+        final InputChannelCompat.InputEventListener eventListener =
+                registerInputEventListener(session);
+
+        session.pop();
+        environment.executeAll();
+
+        final InputEvent event = Mockito.mock(InputEvent.class);
+        environment.publishInputEvent(event);
+
+        verify(eventListener, never()).onInputEvent(eq(event));
+    }
+
+    @Test
     public void testInputGesturePropagation() {
         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 6e002f5..f21ea3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -54,12 +54,15 @@
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.log.SessionTracker
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.phone.FakeKeyguardStateController
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.captureMany
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
@@ -81,6 +84,7 @@
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.isNull
 import org.mockito.Mockito.mock
@@ -120,6 +124,7 @@
     private lateinit var authStatus: FlowValue<AuthenticationStatus?>
     private lateinit var detectStatus: FlowValue<DetectionStatus?>
     private lateinit var authRunning: FlowValue<Boolean?>
+    private lateinit var bypassEnabled: FlowValue<Boolean?>
     private lateinit var lockedOut: FlowValue<Boolean?>
     private lateinit var canFaceAuthRun: FlowValue<Boolean?>
     private lateinit var authenticated: FlowValue<Boolean?>
@@ -180,8 +185,11 @@
     private fun createDeviceEntryFaceAuthRepositoryImpl(
         fmOverride: FaceManager? = faceManager,
         bypassControllerOverride: KeyguardBypassController? = bypassController
-    ) =
-        DeviceEntryFaceAuthRepositoryImpl(
+    ): DeviceEntryFaceAuthRepositoryImpl {
+        val systemClock = FakeSystemClock()
+        val faceAuthBuffer = TableLogBuffer(10, "face auth", systemClock)
+        val faceDetectBuffer = TableLogBuffer(10, "face detect", systemClock)
+        return DeviceEntryFaceAuthRepositoryImpl(
             mContext,
             fmOverride,
             fakeUserRepository,
@@ -197,8 +205,11 @@
             keyguardRepository,
             keyguardInteractor,
             alternateBouncerInteractor,
+            faceDetectBuffer,
+            faceAuthBuffer,
             dumpManager,
         )
+    }
 
     @Test
     fun faceAuthRunsAndProvidesAuthStatusUpdates() =
@@ -726,6 +737,23 @@
         }
 
     @Test
+    fun isBypassEnabledReflectsBypassControllerState() =
+        testScope.runTest {
+            initCollectors()
+            runCurrent()
+            val listeners = captureMany {
+                verify(bypassController, atLeastOnce())
+                    .registerOnBypassStateChangedListener(capture())
+            }
+
+            listeners.forEach { it.onBypassStateChanged(true) }
+            assertThat(bypassEnabled()).isTrue()
+
+            listeners.forEach { it.onBypassStateChanged(false) }
+            assertThat(bypassEnabled()).isFalse()
+        }
+
+    @Test
     fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() =
         testScope.runTest {
             testGatingCheckForDetect {
@@ -844,6 +872,7 @@
         lockedOut = collectLastValue(underTest.isLockedOut)
         canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth)
         authenticated = collectLastValue(underTest.isAuthenticated)
+        bypassEnabled = collectLastValue(underTest.isBypassEnabled)
         fakeUserRepository.setSelectedUserInfo(primaryUser)
     }
 
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
new file mode 100644
index 0000000..3d1d2f4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -0,0 +1,292 @@
+/*
+ *  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.keyguard.domain.interactor
+
+import android.os.Handler
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: SystemUIKeyguardFaceAuthInteractor
+    private lateinit var testScope: TestScope
+    private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository
+
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        val scheduler = TestCoroutineScheduler()
+        val dispatcher = StandardTestDispatcher(scheduler)
+        testScope = TestScope(dispatcher)
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.FACE_AUTH_REFACTOR, true)
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+        keyguardTransitionInteractor = KeyguardTransitionInteractor(keyguardTransitionRepository)
+
+        underTest =
+            SystemUIKeyguardFaceAuthInteractor(
+                testScope.backgroundScope,
+                dispatcher,
+                faceAuthRepository,
+                PrimaryBouncerInteractor(
+                    bouncerRepository,
+                    mock(BouncerView::class.java),
+                    mock(Handler::class.java),
+                    mock(KeyguardStateController::class.java),
+                    mock(KeyguardSecurityModel::class.java),
+                    mock(PrimaryBouncerCallbackInteractor::class.java),
+                    mock(FalsingCollector::class.java),
+                    mock(DismissCallbackRegistry::class.java),
+                    context,
+                    keyguardUpdateMonitor,
+                    mock(KeyguardBypassController::class.java),
+                ),
+                AlternateBouncerInteractor(
+                    mock(StatusBarStateController::class.java),
+                    mock(KeyguardStateController::class.java),
+                    bouncerRepository,
+                    mock(BiometricSettingsRepository::class.java),
+                    FakeDeviceEntryFingerprintAuthRepository(),
+                    FakeSystemClock(),
+                ),
+                keyguardTransitionInteractor,
+                featureFlags,
+                FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
+                keyguardUpdateMonitor,
+            )
+    }
+
+    @Test
+    fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromOffState() =
+        testScope.runTest {
+            underTest.start()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.OFF,
+                    KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.STARTED
+                )
+            )
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() =
+        testScope.runTest {
+            underTest.start()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.AOD,
+                    KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.STARTED
+                )
+            )
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromDozingState() =
+        testScope.runTest {
+            underTest.start()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.DOZING,
+                    KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.STARTED
+                )
+            )
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenPrimaryBouncerIsVisible() =
+        testScope.runTest {
+            underTest.start()
+
+            bouncerRepository.setPrimaryShow(false)
+            runCurrent()
+
+            bouncerRepository.setPrimaryShow(true)
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, true))
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenAlternateBouncerIsVisible() =
+        testScope.runTest {
+            underTest.start()
+
+            bouncerRepository.setAlternateVisible(false)
+            runCurrent()
+
+            bouncerRepository.setAlternateVisible(true)
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(
+                        FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
+                        false
+                    )
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenUdfpsSensorTouched() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onUdfpsSensorTouched()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false))
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenOnAssistantTriggeredOnLockScreen() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onAssistantTriggeredOnLockScreen()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenDeviceLifted() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onDeviceLifted()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenQsExpansionStared() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onQsExpansionStared()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true))
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenNotificationPanelClicked() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onNotificationPanelClicked()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenSwipeUpOnBouncer() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onSwipeUpOnBouncer()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false))
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index aace566..1e465c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -24,6 +24,7 @@
 import android.content.Intent
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
+import android.content.res.Configuration
 import android.graphics.Bitmap
 import android.graphics.Canvas
 import android.graphics.Color
@@ -42,6 +43,7 @@
 import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.util.TypedValue
 import android.view.View
 import android.view.ViewGroup
 import android.view.animation.Interpolator
@@ -101,7 +103,6 @@
 import junit.framework.Assert.assertTrue
 import org.junit.After
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -2200,8 +2201,7 @@
     }
 
     @Test
-    @Ignore("b/276920368")
-    fun bindRecommendation_carouselNotFitThreeRecs() {
+    fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() {
         useRealConstraintSets()
         setupUpdatedRecommendationViewHolder()
         val albumArt = getColorIcon(Color.RED)
@@ -2229,16 +2229,84 @@
 
         // set the screen width less than the width of media controls.
         player.context.resources.configuration.screenWidthDp = 350
+        player.context.resources.configuration.orientation = Configuration.ORIENTATION_PORTRAIT
         player.attachRecommendation(recommendationViewHolder)
         player.bindRecommendation(data)
 
-        assertThat(player.numberOfFittedRecommendations).isEqualTo(2)
-        assertThat(expandedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE)
-        assertThat(collapsedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE)
-        assertThat(expandedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE)
-        assertThat(collapsedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE)
-        assertThat(expandedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE)
-        assertThat(collapsedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE)
+        val res = player.context.resources
+        val displayAvailableWidth =
+            TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt()
+        val recCoverWidth: Int =
+            (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
+                res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
+        val numOfRecs = displayAvailableWidth / recCoverWidth
+
+        assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs)
+        recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container ->
+            if (index < numOfRecs) {
+                assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE)
+                assertThat(collapsedSet.getVisibility(container.id))
+                    .isEqualTo(ConstraintSet.VISIBLE)
+            } else {
+                assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+                assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+            }
+        }
+    }
+
+    @Test
+    fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() {
+        useRealConstraintSets()
+        setupUpdatedRecommendationViewHolder()
+        val albumArt = getColorIcon(Color.RED)
+        val data =
+            smartspaceData.copy(
+                recommendations =
+                    listOf(
+                        SmartspaceAction.Builder("id1", "title1")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id2", "title2")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id3", "title3")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(Bundle.EMPTY)
+                            .build()
+                    )
+            )
+
+        // set the screen width less than the width of media controls.
+        // We should have dp width less than 378 to test. In landscape we should have 2x.
+        player.context.resources.configuration.screenWidthDp = 700
+        player.context.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+        player.attachRecommendation(recommendationViewHolder)
+        player.bindRecommendation(data)
+
+        val res = player.context.resources
+        val displayAvailableWidth =
+            TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt()
+        val recCoverWidth: Int =
+            (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
+                res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
+        val numOfRecs = displayAvailableWidth / recCoverWidth
+
+        assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs)
+        recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container ->
+            if (index < numOfRecs) {
+                assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE)
+                assertThat(collapsedSet.getVisibility(container.id))
+                    .isEqualTo(ConstraintSet.VISIBLE)
+            } else {
+                assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+                assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+            }
+        }
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index aa92177..e4d8b25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -46,7 +46,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.CollectionUtils;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -117,8 +116,6 @@
     @Mock
     private CustomTile mCustomTile;
     @Mock
-    private UiEventLogger mUiEventLogger;
-    @Mock
     private UserTracker mUserTracker;
     private SecureSettings mSecureSettings;
     @Mock
@@ -164,7 +161,7 @@
         saveSetting("");
         mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
                 mPluginManager, mTunerService, mAutoTiles, mCentralSurfaces,
-                mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
+                mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
                 mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags);
 
         mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
@@ -684,14 +681,14 @@
                 QSFactory defaultFactory, Executor mainExecutor,
                 PluginManager pluginManager, TunerService tunerService,
                 Provider<AutoTileManager> autoTiles,
-                CentralSurfaces centralSurfaces, QSLogger qsLogger, UiEventLogger uiEventLogger,
+                CentralSurfaces centralSurfaces, QSLogger qsLogger,
                 UserTracker userTracker, SecureSettings secureSettings,
                 CustomTileStatePersister customTileStatePersister,
                 TileLifecycleManager.Factory tileLifecycleManagerFactory,
                 UserFileManager userFileManager, FeatureFlags featureFlags) {
             super(context, defaultFactory, mainExecutor, pluginManager,
                     tunerService, autoTiles,  Optional.of(centralSurfaces), qsLogger,
-                    uiEventLogger, userTracker, secureSettings, customTileStatePersister,
+                    userTracker, secureSettings, customTileStatePersister,
                     tileLifecycleManagerFactory, userFileManager, featureFlags);
         }
 
@@ -710,6 +707,7 @@
         protected TestTile(QSHost host) {
             super(
                     host,
+                    mock(QsEventLogger.class),
                     mock(Looper.class),
                     mock(Handler.class),
                     new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt
new file mode 100644
index 0000000..40aa260
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt
@@ -0,0 +1,35 @@
+/*
+ * 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
+
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.InstanceIdSequenceFake
+
+class QsEventLoggerFake(
+    uiEventLogger: UiEventLoggerFake,
+    private val instanceIdSequence: InstanceIdSequenceFake,
+) : QsEventLogger, UiEventLogger by uiEventLogger {
+
+    val lastInstanceId: Int
+        get() = instanceIdSequence.lastInstanceId
+
+    override fun getNewInstanceId(): InstanceId {
+        return instanceIdSequence.newInstanceId()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index ac106ef..198ed4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -41,6 +41,7 @@
 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.settings.FakeDisplayTracker
 import com.android.systemui.util.mockito.any
@@ -56,12 +57,12 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.clearInvocations
 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
 
 @SmallTest
@@ -89,6 +90,7 @@
     @Mock private lateinit var applicationInfo: ApplicationInfo
     @Mock private lateinit var serviceInfo: ServiceInfo
     @Mock private lateinit var customTileStatePersister: CustomTileStatePersister
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
     private var displayTracker = FakeDisplayTracker(mContext)
     private lateinit var customTile: CustomTile
@@ -115,6 +117,7 @@
 
         customTileBuilder = CustomTile.Builder(
                 { tileHost },
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 36549fb..962b537 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -61,6 +61,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import com.android.systemui.InstanceIdSequenceFake;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
@@ -69,6 +70,8 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSEvent;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.QsEventLoggerFake;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.StatusBarState;
 
@@ -106,7 +109,8 @@
     private ActivityStarter mActivityStarter;
 
     private UiEventLoggerFake mUiEventLoggerFake;
-    private InstanceId mInstanceId = InstanceId.fakeInstanceId(5);
+    private QsEventLoggerFake mQsEventLoggerFake;
+    private InstanceId mInstanceId;
 
     @Captor
     private ArgumentCaptor<LogMaker> mLogCaptor;
@@ -115,18 +119,29 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
+
         mUiEventLoggerFake = new UiEventLoggerFake();
+        mQsEventLoggerFake =
+                new QsEventLoggerFake(mUiEventLoggerFake, new InstanceIdSequenceFake(10));
         when(mHost.indexOf(SPEC)).thenReturn(POSITION);
         when(mHost.getContext()).thenReturn(mContext);
-        when(mHost.getUiEventLogger()).thenReturn(mUiEventLoggerFake);
-        when(mHost.getNewInstanceId()).thenReturn(mInstanceId);
 
         Handler mainHandler = new Handler(mTestableLooper.getLooper());
 
-        mTile = new TileImpl(mHost, mTestableLooper.getLooper(), mainHandler, mFalsingManager,
-                mMetricsLogger, mStatusBarStateController, mActivityStarter, mQsLogger);
+        mTile = new TileImpl(
+                mHost,
+                mQsEventLoggerFake,
+                mTestableLooper.getLooper(),
+                mainHandler,
+                mFalsingManager,
+                mMetricsLogger,
+                mStatusBarStateController,
+                mActivityStarter,
+                mQsLogger
+        );
         mTile.initialize();
         mTestableLooper.processAllMessages();
+        mInstanceId = InstanceId.fakeInstanceId(mQsEventLoggerFake.getLastInstanceId());
 
         mTile.setTileSpec(SPEC);
     }
@@ -507,6 +522,7 @@
 
         protected TileImpl(
                 QSHost host,
+                QsEventLogger uiEventLogger,
                 Looper backgroundLooper,
                 Handler mainHandler,
                 FalsingManager falsingManager,
@@ -515,7 +531,7 @@
                 ActivityStarter activityStarter,
                 QSLogger qsLogger
         ) {
-            super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+            super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                     statusBarStateController, activityStarter, qsLogger);
             getState().state = Tile.STATE_ACTIVE;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index 5e0190b..c60cecb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -22,8 +22,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -32,6 +30,7 @@
 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.settings.UserTracker
@@ -68,20 +67,21 @@
     private lateinit var mGlobalSettings: GlobalSettings
     @Mock
     private lateinit var mUserTracker: UserTracker
+    @Mock
+    private lateinit var mUiEventLogger: QsEventLogger
     private lateinit var mTestableLooper: TestableLooper
     private lateinit var mTile: AirplaneModeTile
 
-    private val mUiEventLogger: UiEventLogger = UiEventLoggerFake()
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         mTestableLooper = TestableLooper.get(this)
         Mockito.`when`(mHost.context).thenReturn(mContext)
-        Mockito.`when`(mHost.uiEventLogger).thenReturn(mUiEventLogger)
         Mockito.`when`(mHost.userContext).thenReturn(mContext)
 
-        mTile = AirplaneModeTile(mHost,
+        mTile = AirplaneModeTile(
+            mHost,
+            mUiEventLogger,
             mTestableLooper.looper,
             Handler(mTestableLooper.looper),
             FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
index f1e3e8a..52b8455 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
@@ -9,12 +9,12 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
 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.settings.UserTracker
 import com.android.systemui.statusbar.policy.NextAlarmController
@@ -28,8 +28,8 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -52,7 +52,7 @@
     @Mock
     private lateinit var nextAlarmController: NextAlarmController
     @Mock
-    private lateinit var uiEventLogger: UiEventLogger
+    private lateinit var uiEventLogger: QsEventLogger
     @Mock
     private lateinit var pendingIntent: PendingIntent
     @Captor
@@ -67,10 +67,10 @@
         testableLooper = TestableLooper.get(this)
 
         `when`(qsHost.context).thenReturn(mContext)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile = AlarmTile(
             qsHost,
+            uiEventLogger,
             testableLooper.looper,
             Handler(testableLooper.looper),
             FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
index a5c0004..ff6814c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
@@ -31,6 +31,7 @@
 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.statusbar.policy.BatteryController
@@ -43,10 +44,10 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -63,6 +64,8 @@
     @Mock
     private lateinit var qsHost: QSHost
     @Mock
+    private lateinit var uiEventLogger: QsEventLogger
+    @Mock
     private lateinit var metricsLogger: MetricsLogger
     @Mock
     private lateinit var statusBarStateController: StatusBarStateController
@@ -90,6 +93,7 @@
 
         tile = BatterySaverTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 2e77de2..5e7f68c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -9,7 +9,6 @@
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.settingslib.Utils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.systemui.R
@@ -20,6 +19,7 @@
 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.statusbar.policy.BluetoothController
@@ -49,8 +49,8 @@
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var bluetoothController: BluetoothController
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
-    private val uiEventLogger = UiEventLoggerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: FakeBluetoothTile
 
@@ -60,11 +60,11 @@
         testableLooper = TestableLooper.get(this)
 
         whenever(qsHost.context).thenReturn(mContext)
-        whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile =
             FakeBluetoothTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 falsingManager,
@@ -211,6 +211,7 @@
 
     private class FakeBluetoothTile(
         qsHost: QSHost,
+        uiEventLogger: QsEventLogger,
         backgroundLooper: Looper,
         mainHandler: Handler,
         falsingManager: FalsingManager,
@@ -222,6 +223,7 @@
     ) :
         BluetoothTile(
             qsHost,
+            uiEventLogger,
             backgroundLooper,
             mainHandler,
             falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
index 4193854..70d82fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
@@ -21,8 +21,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -30,6 +28,7 @@
 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.QsEventLoggerFake
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
@@ -67,19 +66,21 @@
     private lateinit var privacyController: IndividualSensorPrivacyController
     @Mock
     private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var uiEventLogger: QsEventLoggerFake
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: CameraToggleTile
-    private val uiEventLogger: UiEventLogger = UiEventLoggerFake()
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         whenever(host.context).thenReturn(mContext)
-        whenever(host.uiEventLogger).thenReturn(uiEventLogger)
 
-        tile = CameraToggleTile(host,
+        tile = CameraToggleTile(
+                host,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 metricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 64fd09d5..93ed994 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -43,6 +43,7 @@
 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.statusbar.connectivity.IconState;
 import com.android.systemui.statusbar.connectivity.NetworkController;
@@ -94,6 +95,8 @@
     private QSLogger mQSLogger;
     @Mock
     private DialogLaunchAnimator mDialogLaunchAnimator;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private CastTile mCastTile;
@@ -107,6 +110,7 @@
 
         mCastTile = new CastTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
index 13c30e9..2250ef3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
@@ -32,12 +32,12 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
 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.settings.UserTracker;
 import com.android.systemui.util.settings.FakeSettings;
@@ -67,7 +67,7 @@
     @Mock
     private QSLogger mQSLogger;
     @Mock
-    private UiEventLogger mUiEventLogger;
+    private QsEventLogger mUiEventLogger;
     @Mock
     private UserTracker mUserTracker;
 
@@ -83,10 +83,10 @@
         mTestableLooper = TestableLooper.get(this);
 
         when(mHost.getContext()).thenReturn(mContext);
-        when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
 
         mTile = new ColorCorrectionTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
index ff27e02..2e02bbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
@@ -32,7 +32,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -40,6 +39,7 @@
 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.settings.UserTracker;
@@ -72,7 +72,7 @@
     @Mock
     private QSLogger mQSLogger;
     @Mock
-    private UiEventLogger mUiEventLogger;
+    private QsEventLogger mUiEventLogger;
     @Mock
     private UserTracker mUserTracker;
 
@@ -88,10 +88,10 @@
         mTestableLooper = TestableLooper.get(this);
 
         when(mHost.getContext()).thenReturn(mContext);
-        when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
 
         mTile = new ColorInversionTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
index b048643..176b33f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
@@ -21,7 +21,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
@@ -30,6 +29,7 @@
 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.statusbar.policy.DataSaverController
@@ -57,8 +57,8 @@
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var dataSaverController: DataSaverController
     @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
-    private val uiEventLogger = UiEventLoggerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: DataSaverTile
 
@@ -68,11 +68,11 @@
         testableLooper = TestableLooper.get(this)
 
         Mockito.`when`(mHost.context).thenReturn(mContext)
-        Mockito.`when`(mHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile =
             DataSaverTile(
                 mHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index b51c378..1346069 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -27,7 +27,6 @@
 import androidx.lifecycle.LifecycleOwner
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
@@ -44,6 +43,7 @@
 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.qs.tileimpl.QSTileImpl
 import com.android.systemui.util.mockito.any
@@ -52,6 +52,7 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -59,15 +60,14 @@
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.nullable
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
 import java.util.Optional
-import org.junit.After
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -95,7 +95,7 @@
     @Mock
     private lateinit var serviceInfo: ControlsServiceInfo
     @Mock
-    private lateinit var uiEventLogger: UiEventLogger
+    private lateinit var uiEventLogger: QsEventLogger
     @Captor
     private lateinit var listingCallbackCaptor:
             ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@@ -118,7 +118,6 @@
         spiedContext = spy(mContext)
         doNothing().`when`(spiedContext).startActivity(any(Intent::class.java))
         `when`(qsHost.context).thenReturn(spiedContext)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
         `when`(controlsComponent.isEnabled()).thenReturn(true)
         `when`(controlsController.getPreferredSelection())
                 .thenReturn(SelectedItem.StructureItem(
@@ -399,6 +398,7 @@
     private fun createTile(): DeviceControlsTile {
         return DeviceControlsTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
index 6c0904e..f0e4e3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
@@ -28,7 +28,6 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
@@ -37,6 +36,7 @@
 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.statusbar.policy.ZenModeController
@@ -46,7 +46,6 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
-import java.io.File
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -55,8 +54,9 @@
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import java.io.File
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -84,7 +84,7 @@
     private lateinit var qsLogger: QSLogger
 
     @Mock
-    private lateinit var uiEventLogger: UiEventLogger
+    private lateinit var uiEventLogger: QsEventLogger
 
     @Mock
     private lateinit var zenModeController: ZenModeController
@@ -109,7 +109,6 @@
         secureSettings = FakeSettings()
 
         whenever(qsHost.userId).thenReturn(DEFAULT_USER)
-        whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         val wrappedContext = object : ContextWrapper(context) {
             override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences {
@@ -120,6 +119,7 @@
 
         tile = DndTile(
             qsHost,
+            uiEventLogger,
             testableLooper.looper,
             Handler(testableLooper.looper),
             FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
index 7d41aa6..f231c6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
@@ -48,6 +48,7 @@
 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.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
@@ -83,6 +84,8 @@
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
     private UserTracker mUserTracker;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
 
@@ -258,6 +261,7 @@
             boolean dreamOnlyEnabledForSystemUser) {
         return new DreamTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
index 692a644..73aa699 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
@@ -6,7 +6,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -14,6 +13,7 @@
 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.statusbar.policy.FlashlightController
@@ -45,7 +45,8 @@
 
     @Mock private lateinit var flashlightController: FlashlightController
 
-    private val uiEventLogger = UiEventLoggerFake()
+    @Mock private lateinit var uiEventLogger: QsEventLogger
+
     private val falsingManager = FalsingManagerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: FlashlightTile
@@ -56,11 +57,11 @@
         testableLooper = TestableLooper.get(this)
 
         Mockito.`when`(qsHost.context).thenReturn(mockContext)
-        Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile =
             FlashlightTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
index eeebd4f..1d6f225 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
@@ -23,7 +23,6 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.classifier.FalsingManagerFake
@@ -31,7 +30,8 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -52,13 +52,13 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class FontScalingTileTest : SysuiTestCase() {
-    @Mock private lateinit var qsHost: QSTileHost
+    @Mock private lateinit var qsHost: QSHost
     @Mock private lateinit var metricsLogger: MetricsLogger
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var qsLogger: QSLogger
     @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
-    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var fontScalingTile: FontScalingTile
@@ -70,11 +70,11 @@
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         `when`(qsHost.getContext()).thenReturn(mContext)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         fontScalingTile =
             FontScalingTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
index 959e750..73f61d06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
@@ -38,6 +38,7 @@
 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.statusbar.policy.DataSaverController;
@@ -66,6 +67,8 @@
     private HotspotController mHotspotController;
     @Mock
     private DataSaverController mDataSaverController;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private HotspotTile mTile;
@@ -80,6 +83,7 @@
 
         mTile = new HotspotTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index adfd7f7..7957c6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -35,6 +35,7 @@
 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.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
@@ -63,6 +64,8 @@
     private AccessPointController mAccessPointController;
     @Mock
     private InternetDialogFactory mInternetDialogFactory;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private InternetTile mTile;
@@ -76,6 +79,7 @@
 
         mTile = new InternetTile(
             mHost,
+            mUiEventLogger,
             mTestableLooper.getLooper(),
             new Handler(mTestableLooper.getLooper()),
             new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
index 3642e87..0bf0b38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
@@ -22,7 +22,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -30,6 +29,7 @@
 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.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -43,8 +43,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -71,8 +71,9 @@
     private lateinit var keyguardStateController: KeyguardStateController
     @Mock
     private lateinit var panelInteractor: PanelInteractor
+    @Mock
+    private lateinit var uiEventLogger: QsEventLogger
 
-    private val uiEventLogger = UiEventLoggerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: LocationTile
 
@@ -80,10 +81,11 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
         `when`(qsHost.context).thenReturn(mockContext)
 
-        tile = LocationTile(qsHost,
+        tile = LocationTile(
+            qsHost,
+            uiEventLogger,
             testableLooper.looper,
             Handler(testableLooper.looper),
             falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
index e2f64b2..ceff546 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
@@ -21,8 +21,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -30,6 +28,7 @@
 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.statusbar.policy.IndividualSensorPrivacyController
@@ -67,19 +66,22 @@
     private lateinit var privacyController: IndividualSensorPrivacyController
     @Mock
     private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var uiEventLogger: QsEventLogger
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: MicrophoneToggleTile
-    private val uiEventLogger: UiEventLogger = UiEventLoggerFake()
+
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         whenever(host.context).thenReturn(mContext)
-        whenever(host.uiEventLogger).thenReturn(uiEventLogger)
 
-        tile = MicrophoneToggleTile(host,
+        tile = MicrophoneToggleTile(
+                host,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 metricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
index c7dae83..763a7e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
@@ -37,6 +37,7 @@
 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 org.junit.After;
@@ -70,6 +71,8 @@
     private QSLogger mQSLogger;
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private NfcTile mNfcTile;
@@ -84,6 +87,7 @@
 
         mNfcTile = new NfcTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
index 04af69c..6c8f76b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
@@ -23,8 +23,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -33,6 +31,7 @@
 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.statusbar.policy.LocationController
@@ -43,8 +42,8 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -68,17 +67,18 @@
 
     @Mock private lateinit var mNightDisplayListener: NightDisplayListener
 
+    @Mock private lateinit var mUiEventLogger: QsEventLogger
+
     private lateinit var mTestableLooper: TestableLooper
     private lateinit var mTile: NightDisplayTile
 
-    private val mUiEventLogger: UiEventLogger = UiEventLoggerFake()
+
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         mTestableLooper = TestableLooper.get(this)
         whenever(mHost.context).thenReturn(mContext)
-        whenever(mHost.uiEventLogger).thenReturn(mUiEventLogger)
         whenever(mHost.userContext).thenReturn(mContext)
         whenever(mNightDisplayListenerBuilder.setUser(anyInt()))
             .thenReturn(mNightDisplayListenerBuilder)
@@ -87,6 +87,7 @@
         mTile =
             NightDisplayTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.looper,
                 Handler(mTestableLooper.looper),
                 FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
index 652c138..c391153 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
@@ -33,6 +33,7 @@
 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.settings.UserTracker;
 import com.android.systemui.util.settings.SecureSettings;
@@ -65,6 +66,8 @@
     private UserTracker mUserTracker;
     @Mock
     private SecureSettings mSecureSettings;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private OneHandedModeTile mTile;
@@ -78,6 +81,7 @@
 
         mTile = spy(new OneHandedModeTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index 3125d45..6f2d904 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -30,8 +30,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -40,6 +38,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
 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;
 
@@ -64,7 +63,8 @@
     private ActivityStarter mActivityStarter;
     @Mock
     private QSLogger mQSLogger;
-    private UiEventLogger mUiEventLogger = new UiEventLoggerFake();
+    @Mock
+    private QsEventLogger mUiEventLogger;
     @Mock
     private QRCodeScannerController mController;
 
@@ -79,6 +79,7 @@
 
         mTile = new QRCodeScannerTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 596df78..b089e38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -58,8 +58,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -67,6 +65,7 @@
 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.statusbar.policy.KeyguardStateController;
@@ -109,7 +108,8 @@
     private ActivityStarter mActivityStarter;
     @Mock
     private QSLogger mQSLogger;
-    private UiEventLogger mUiEventLogger = new UiEventLoggerFake();
+    @Mock
+    private QsEventLogger mUiEventLogger;
     @Mock
     private QuickAccessWalletClient mQuickAccessWalletClient;
     @Mock
@@ -136,7 +136,6 @@
 
         doNothing().when(mSpiedContext).startActivity(any(Intent.class));
         when(mHost.getContext()).thenReturn(mSpiedContext);
-        when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
         when(mQuickAccessWalletClient.getServiceLabel()).thenReturn(LABEL);
         when(mQuickAccessWalletClient.getTileIcon()).thenReturn(mTileIcon);
         when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true);
@@ -146,6 +145,7 @@
 
         mTile = new QuickAccessWalletTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
index 7913628..d244594 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
@@ -39,6 +39,7 @@
 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.ReduceBrightColorsController;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -69,6 +70,8 @@
     private UserTracker mUserTracker;
     @Mock
     private ReduceBrightColorsController mReduceBrightColorsController;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private ReduceBrightColorsTile mTile;
@@ -85,6 +88,7 @@
                 true,
                 mReduceBrightColorsController,
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index 5b94cfe..e106741 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -39,6 +39,7 @@
 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.statusbar.policy.BatteryController;
@@ -87,6 +88,8 @@
     DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
     @Mock
     RotationPolicyWrapper mRotationPolicyWrapper;
+    @Mock
+    QsEventLogger mUiEventLogger;
 
     private RotationLockController mController;
     private TestableLooper mTestableLooper;
@@ -105,6 +108,7 @@
 
         mLockTile = new RotationLockTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index d9ed1a2..fff2b8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -44,6 +44,7 @@
 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.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -86,6 +87,8 @@
     private DialogLaunchAnimator mDialogLaunchAnimator;
     @Mock
     private PanelInteractor mPanelInteractor;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private ScreenRecordTile mTile;
@@ -100,6 +103,7 @@
 
         mTile = new ScreenRecordTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
index b556571..79147e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
@@ -25,7 +25,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -33,6 +32,7 @@
 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.statusbar.policy.BatteryController
@@ -63,8 +63,8 @@
     @Mock private lateinit var configurationController: ConfigurationController
     @Mock private lateinit var batteryController: BatteryController
     @Mock private lateinit var locationController: LocationController
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
-    private val uiEventLogger = UiEventLoggerFake()
     private val falsingManager = FalsingManagerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: UiModeNightTile
@@ -81,11 +81,11 @@
         `when`(qsHost.userContext).thenReturn(mContext)
         `when`(mockContext.resources).thenReturn(resources)
         `when`(resources.configuration).thenReturn(configuration)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile =
             UiModeNightTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 5ca3771..4bfd6a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -93,6 +93,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
@@ -665,7 +666,8 @@
                 mMetricsLogger,
                 mFeatureFlags,
                 mInteractionJankMonitor,
-                mShadeLog
+                mShadeLog,
+                mock(KeyguardFaceAuthInteractor.class)
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index d8ffe39..908f7cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -62,6 +62,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
@@ -239,7 +240,8 @@
                 mMetricsLogger,
                 mFeatureFlags,
                 mInteractionJankMonitor,
-                mShadeLogger
+                mShadeLogger,
+                mock(KeyguardFaceAuthInteractor.class)
         );
 
         mFragmentListener = mQsController.getQsFragmentListener();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 569f90b..4438b98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -614,9 +614,8 @@
     public void onBiometricHelp_coEx_faceFailure() {
         createController();
 
-        // GIVEN unlocking with fingerprint is possible
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
-                .thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         String message = "A message";
         mController.setVisible(true);
@@ -641,9 +640,8 @@
     public void onBiometricHelp_coEx_faceUnavailable() {
         createController();
 
-        // GIVEN unlocking with fingerprint is possible
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
-                .thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         String message = "A message";
         mController.setVisible(true);
@@ -664,6 +662,35 @@
                 mContext.getString(R.string.keyguard_suggest_fingerprint));
     }
 
+
+    @Test
+    public void onBiometricHelp_coEx_faceUnavailable_fpNotAllowed() {
+        createController();
+
+        // GIVEN unlocking with fingerprint is possible but not allowed
+        setupFingerprintUnlockPossible(true);
+        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed())
+                .thenReturn(false);
+
+        String message = "A message";
+        mController.setVisible(true);
+
+        // WHEN there's a face unavailable message
+        mController.getKeyguardCallback().onBiometricHelp(
+                BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
+                message,
+                BiometricSourceType.FACE);
+
+        // THEN show sequential messages such as: 'face unlock unavailable' and
+        // 'try fingerprint instead'
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                message);
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
     @Test
     public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() {
         createController();
@@ -818,8 +845,7 @@
     @Test
     public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() {
         createController();
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        fingerprintUnlockIsPossibleAndAllowed();
         String message = "A message";
 
         mController.setVisible(true);
@@ -832,9 +858,8 @@
     public void sendFaceHelpMessages_fingerprintEnrolled() {
         createController();
 
-        // GIVEN fingerprint enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         // WHEN help messages received that are allowed to show
         final String helpString = "helpString";
@@ -859,9 +884,8 @@
     public void doNotSendMostFaceHelpMessages_fingerprintEnrolled() {
         createController();
 
-        // GIVEN fingerprint enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         // WHEN help messages received that aren't supposed to show
         final String helpString = "helpString";
@@ -886,9 +910,8 @@
     public void sendAllFaceHelpMessages_fingerprintNotEnrolled() {
         createController();
 
-        // GIVEN fingerprint NOT enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(false);
+        // GIVEN fingerprint NOT possible
+        fingerprintUnlockIsNotPossible();
 
         // WHEN help messages received
         final Set<CharSequence> helpStrings = new HashSet<>();
@@ -917,9 +940,8 @@
     public void sendTooDarkFaceHelpMessages_onTimeout_noFpEnrolled() {
         createController();
 
-        // GIVEN fingerprint NOT enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(false);
+        // GIVEN fingerprint not possible
+        fingerprintUnlockIsNotPossible();
 
         // WHEN help message received and deferred message is valid
         final String helpString = "helpMsg";
@@ -948,9 +970,8 @@
     public void sendTooDarkFaceHelpMessages_onTimeout_fingerprintEnrolled() {
         createController();
 
-        // GIVEN fingerprint enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         // WHEN help message received and deferredMessage is valid
         final String helpString = "helpMsg";
@@ -1500,7 +1521,7 @@
     @Test
     public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() {
         createController();
-        fingerprintUnlockIsPossible();
+        fingerprintUnlockIsPossibleAndAllowed();
         onFaceLockoutError("first lockout");
 
         verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
@@ -1559,7 +1580,7 @@
     @Test
     public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() {
         createController();
-        fingerprintUnlockIsPossible();
+        fingerprintUnlockIsPossibleAndAllowed();
         onFaceLockoutError("first lockout");
         clearInvocations(mRotateTextViewController);
 
@@ -1668,7 +1689,7 @@
     public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
         createController();
         screenIsTurningOn();
-        fingerprintUnlockIsPossible();
+        fingerprintUnlockIsPossibleAndAllowed();
 
         onFaceLockoutError("lockout error");
         verifyNoMoreInteractions(mRotateTextViewController);
@@ -1746,8 +1767,9 @@
         setupFingerprintUnlockPossible(false);
     }
 
-    private void fingerprintUnlockIsPossible() {
+    private void fingerprintUnlockIsPossibleAndAllowed() {
         setupFingerprintUnlockPossible(true);
+        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(true);
     }
 
     private void setupFingerprintUnlockPossible(boolean possible) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 7b59cc2..08a9f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -22,7 +22,7 @@
 import android.testing.TestableLooper.RunWithLooper
 import android.view.View
 import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule2
+import androidx.core.animation.AnimatorTestRule
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -70,7 +70,7 @@
     private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
     private val fakeFeatureFlags = FakeFeatureFlags()
 
-    @get:Rule val animatorTestRule = AnimatorTestRule2()
+    @get:Rule val animatorTestRule = AnimatorTestRule()
 
     @Before
     fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index be3b723..aff705f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -18,7 +18,7 @@
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import androidx.core.animation.AnimatorTestRule2
+import androidx.core.animation.AnimatorTestRule
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -51,7 +51,7 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
 
-    @get:Rule val animatorTestRule = AnimatorTestRule2()
+    @get:Rule val animatorTestRule = AnimatorTestRule()
 
     private val dumpManager: DumpManager = mock()
     private val headsUpManager: HeadsUpManager = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 7d02219..9186c35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -100,7 +100,6 @@
         mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
 
         FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
-        fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
         fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false);
         mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
new file mode 100644
index 0000000..2cc375b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.shelf.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationShelfInteractorTest : SysuiTestCase() {
+
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+    private val underTest =
+        NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
+
+    @Test
+    fun shelfIsNotStatic_whenKeyguardNotShowing() = runTest {
+        val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+        keyguardRepository.setKeyguardShowing(false)
+
+        assertThat(shelfStatic).isFalse()
+    }
+
+    @Test
+    fun shelfIsNotStatic_whenKeyguardShowingAndNotBypass() = runTest {
+        val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+
+        assertThat(shelfStatic).isFalse()
+    }
+
+    @Test
+    fun shelfIsStatic_whenBypass() = runTest {
+        val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+
+        assertThat(shelfStatic).isTrue()
+    }
+
+    @Test
+    fun shelfOnKeyguard_whenKeyguardShowing() = runTest {
+        val onKeyguard by collectLastValue(underTest.isShowingOnKeyguard)
+
+        keyguardRepository.setKeyguardShowing(true)
+
+        assertThat(onKeyguard).isTrue()
+    }
+
+    @Test
+    fun shelfNotOnKeyguard_whenKeyguardNotShowing() = runTest {
+        val onKeyguard by collectLastValue(underTest.isShowingOnKeyguard)
+
+        keyguardRepository.setKeyguardShowing(false)
+
+        assertThat(onKeyguard).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
new file mode 100644
index 0000000..439edaf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationShelfViewModelTest : SysuiTestCase() {
+
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+    private val interactor =
+        NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
+    private val underTest = NotificationShelfViewModel(interactor)
+
+    @Test
+    fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest {
+        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+        keyguardRepository.setKeyguardShowing(false)
+
+        assertThat(canModifyNotifColor).isTrue()
+    }
+
+    @Test
+    fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() = runTest {
+        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+
+        assertThat(canModifyNotifColor).isTrue()
+    }
+
+    @Test
+    fun cannotModifyColorOfNotifications_whenBypass() = runTest {
+        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+
+        assertThat(canModifyNotifColor).isFalse()
+    }
+
+    @Test
+    fun isClickable_whenKeyguardShowing() = runTest {
+        val isClickable by collectLastValue(underTest.isClickable)
+
+        keyguardRepository.setKeyguardShowing(true)
+
+        assertThat(isClickable).isTrue()
+    }
+
+    @Test
+    fun isNotClickable_whenKeyguardNotShowing() = runTest {
+        val isClickable by collectLastValue(underTest.isClickable)
+
+        keyguardRepository.setKeyguardShowing(false)
+
+        assertThat(isClickable).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index e6f10cd..5279740 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -48,6 +49,7 @@
     @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
     @Mock
     private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
+    @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var stackLayout: NotificationStackScrollLayout
 
     private val testableResources = mContext.orCreateTestableResources
@@ -67,7 +69,9 @@
             NotificationStackSizeCalculator(
                 statusBarStateController = sysuiStatusBarStateController,
                 lockscreenShadeTransitionController = lockscreenShadeTransitionController,
-                testableResources.resources)
+                mediaDataManager = mediaDataManager,
+                testableResources.resources
+            )
     }
 
     @Test
@@ -76,7 +80,11 @@
 
         val maxNotifications =
             computeMaxKeyguardNotifications(
-                rows, spaceForNotifications = 0f, spaceForShelf = 0f, shelfHeight = 0f)
+                rows,
+                spaceForNotifications = 0f,
+                spaceForShelf = 0f,
+                shelfHeight = 0f
+            )
 
         assertThat(maxNotifications).isEqualTo(0)
     }
@@ -91,7 +99,8 @@
                 rows,
                 spaceForNotifications = Float.MAX_VALUE,
                 spaceForShelf = Float.MAX_VALUE,
-                shelfHeight)
+                shelfHeight
+            )
 
         assertThat(maxNotifications).isEqualTo(numberOfRows)
     }
@@ -111,6 +120,28 @@
     }
 
     @Test
+    fun computeMaxKeyguardNotifications_onLockscreenSpaceForMinHeightButNotIntrinsicHeight_returnsOne() {
+        setGapHeight(0f)
+        // No divider height since we're testing one element where index = 0
+
+        whenever(sysuiStatusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(lockscreenShadeTransitionController.fractionToShade).thenReturn(0f)
+
+        val row = createMockRow(10f, isSticky = true)
+        whenever(row.getMinHeight(any())).thenReturn(5)
+
+        val maxNotifications =
+            computeMaxKeyguardNotifications(
+                listOf(row),
+                /* spaceForNotifications= */ 5f,
+                /* spaceForShelf= */ 0f,
+                /* shelfHeight= */ 0f
+            )
+
+        assertThat(maxNotifications).isEqualTo(1)
+    }
+
+    @Test
     fun computeMaxKeyguardNotifications_spaceForTwo_returnsTwo() {
         setGapHeight(gapHeight)
         val shelfHeight = shelfHeight + dividerHeight
@@ -126,7 +157,11 @@
 
         val maxNotifications =
             computeMaxKeyguardNotifications(
-                rows, spaceForNotifications + 1, spaceForShelf, shelfHeight)
+                rows,
+                spaceForNotifications + 1,
+                spaceForShelf,
+                shelfHeight
+            )
 
         assertThat(maxNotifications).isEqualTo(2)
     }
@@ -136,24 +171,23 @@
         // Each row in separate section.
         setGapHeight(gapHeight)
 
-        val spaceForNotifications =
+        val notifSpace =
             listOf(
                     rowHeight,
                     dividerHeight + gapHeight + rowHeight,
                 )
                 .sum()
 
-        val spaceForShelf = dividerHeight + gapHeight + shelfHeight
-        val spaceUsed = spaceForNotifications + spaceForShelf
+        val shelfSpace = dividerHeight + gapHeight + shelfHeight
+        val spaceUsed = notifSpace + shelfSpace
         val rows =
             listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight))
 
         val maxNotifications =
-            computeMaxKeyguardNotifications(rows, spaceForNotifications, spaceForShelf, shelfHeight)
+            computeMaxKeyguardNotifications(rows, notifSpace, shelfSpace, shelfHeight)
         assertThat(maxNotifications).isEqualTo(2)
 
-        val height =
-            sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
+        val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
         assertThat(height).isEqualTo(spaceUsed)
     }
 
@@ -170,11 +204,14 @@
         // test that we only use space required
         val maxNotifications =
             computeMaxKeyguardNotifications(
-                rows, spaceForNotifications + 1, spaceForShelf, shelfHeight)
+                rows,
+                spaceForNotifications + 1,
+                spaceForShelf,
+                shelfHeight
+            )
         assertThat(maxNotifications).isEqualTo(1)
 
-        val height =
-            sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
+        val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
         assertThat(height).isEqualTo(spaceUsed)
     }
 
@@ -200,7 +237,65 @@
     }
 
     @Test
-    fun spaceNeeded_onLockscreen_usesMinHeight() {
+    fun getSpaceNeeded_onLockscreenEnoughSpaceStickyHun_intrinsicHeight() {
+        setGapHeight(0f)
+        // No divider height since we're testing one element where index = 0
+
+        val row = createMockRow(10f, isSticky = true)
+        whenever(row.getMinHeight(any())).thenReturn(5)
+
+        val space =
+            sizeCalculator.getSpaceNeeded(
+                row,
+                visibleIndex = 0,
+                previousView = null,
+                stack = stackLayout,
+                onLockscreen = true
+            )
+        assertThat(space.whenEnoughSpace).isEqualTo(10f)
+    }
+
+    @Test
+    fun getSpaceNeeded_onLockscreenEnoughSpaceNotStickyHun_minHeight() {
+        setGapHeight(0f)
+        // No divider height since we're testing one element where index = 0
+
+        val row = createMockRow(rowHeight)
+        whenever(row.heightWithoutLockscreenConstraints).thenReturn(10)
+        whenever(row.getMinHeight(any())).thenReturn(5)
+
+        val space =
+            sizeCalculator.getSpaceNeeded(
+                row,
+                visibleIndex = 0,
+                previousView = null,
+                stack = stackLayout,
+                onLockscreen = true
+            )
+        assertThat(space.whenEnoughSpace).isEqualTo(5)
+    }
+
+    @Test
+    fun getSpaceNeeded_onLockscreenSavingSpaceStickyHun_minHeight() {
+        setGapHeight(0f)
+        // No divider height since we're testing one element where index = 0
+
+        val expandableView = createMockRow(10f, isSticky = true)
+        whenever(expandableView.getMinHeight(any())).thenReturn(5)
+
+        val space =
+            sizeCalculator.getSpaceNeeded(
+                expandableView,
+                visibleIndex = 0,
+                previousView = null,
+                stack = stackLayout,
+                onLockscreen = true
+            )
+        assertThat(space.whenSavingSpace).isEqualTo(5)
+    }
+
+    @Test
+    fun getSpaceNeeded_onLockscreenSavingSpaceNotStickyHun_minHeight() {
         setGapHeight(0f)
         // No divider height since we're testing one element where index = 0
 
@@ -209,51 +304,34 @@
         whenever(expandableView.intrinsicHeight).thenReturn(10)
 
         val space =
-            sizeCalculator.spaceNeeded(
+            sizeCalculator.getSpaceNeeded(
                 expandableView,
                 visibleIndex = 0,
                 previousView = null,
                 stack = stackLayout,
-                onLockscreen = true)
-        assertThat(space).isEqualTo(5)
+                onLockscreen = true
+            )
+        assertThat(space.whenSavingSpace).isEqualTo(5)
     }
 
     @Test
-    fun spaceNeeded_fsiHunOnLockscreen_usesIntrinsicHeight() {
-        setGapHeight(0f)
-        // No divider height since we're testing one element where index = 0
-
-        val expandableView = createMockStickyRow(rowHeight)
-        whenever(expandableView.getMinHeight(any())).thenReturn(5)
-        whenever(expandableView.intrinsicHeight).thenReturn(10)
-
-        val space =
-                sizeCalculator.spaceNeeded(
-                        expandableView,
-                        visibleIndex = 0,
-                        previousView = null,
-                        stack = stackLayout,
-                        onLockscreen = true)
-        assertThat(space).isEqualTo(10)
-    }
-
-    @Test
-    fun spaceNeeded_notOnLockscreen_usesIntrinsicHeight() {
+    fun getSpaceNeeded_notOnLockscreen_intrinsicHeight() {
         setGapHeight(0f)
         // No divider height since we're testing one element where index = 0
 
         val expandableView = createMockRow(rowHeight)
-        whenever(expandableView.getMinHeight(any())).thenReturn(5)
-        whenever(expandableView.intrinsicHeight).thenReturn(10)
+        whenever(expandableView.getMinHeight(any())).thenReturn(1)
 
         val space =
-            sizeCalculator.spaceNeeded(
+            sizeCalculator.getSpaceNeeded(
                 expandableView,
                 visibleIndex = 0,
                 previousView = null,
                 stack = stackLayout,
-                onLockscreen = false)
-        assertThat(space).isEqualTo(10)
+                onLockscreen = false
+            )
+        assertThat(space.whenEnoughSpace).isEqualTo(rowHeight)
+        assertThat(space.whenSavingSpace).isEqualTo(rowHeight)
     }
 
     private fun computeMaxKeyguardNotifications(
@@ -264,7 +342,11 @@
     ): Int {
         setupChildren(rows)
         return sizeCalculator.computeMaxKeyguardNotifications(
-            stackLayout, spaceForNotifications, spaceForShelf, shelfHeight)
+            stackLayout,
+            spaceForNotifications,
+            spaceForShelf,
+            shelfHeight
+        )
     }
 
     private fun setupChildren(children: List<ExpandableView>) {
@@ -280,11 +362,13 @@
 
     private fun createMockRow(
         height: Float = rowHeight,
+        isSticky: Boolean = false,
         isRemoved: Boolean = false,
-        visibility: Int = VISIBLE
+        visibility: Int = VISIBLE,
     ): ExpandableNotificationRow {
         val row = mock(ExpandableNotificationRow::class.java)
         val entry = mock(NotificationEntry::class.java)
+        whenever(entry.isStickyAndNotDemoted).thenReturn(isSticky)
         val sbn = mock(StatusBarNotification::class.java)
         whenever(entry.sbn).thenReturn(sbn)
         whenever(row.entry).thenReturn(entry)
@@ -292,25 +376,7 @@
         whenever(row.visibility).thenReturn(visibility)
         whenever(row.getMinHeight(any())).thenReturn(height.toInt())
         whenever(row.intrinsicHeight).thenReturn(height.toInt())
-        return row
-    }
-
-    private fun createMockStickyRow(
-            height: Float = rowHeight,
-            isRemoved: Boolean = false,
-            visibility: Int = VISIBLE
-    ): ExpandableNotificationRow {
-        val row = mock(ExpandableNotificationRow::class.java)
-        val entry = mock(NotificationEntry::class.java)
-        whenever(entry.isStickyAndNotDemoted).thenReturn(true)
-
-        val sbn = mock(StatusBarNotification::class.java)
-        whenever(entry.sbn).thenReturn(sbn)
-        whenever(row.entry).thenReturn(entry)
-        whenever(row.isRemoved).thenReturn(isRemoved)
-        whenever(row.visibility).thenReturn(visibility)
-        whenever(row.getMinHeight(any())).thenReturn(height.toInt())
-        whenever(row.intrinsicHeight).thenReturn(height.toInt())
+        whenever(row.heightWithoutLockscreenConstraints).thenReturn(height.toInt())
         return row
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 14aee4e..baeb188 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -20,6 +20,7 @@
 import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -73,6 +74,7 @@
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeController;
@@ -192,7 +194,8 @@
                         mPrimaryBouncerInteractor,
                         mBouncerView,
                         mAlternateBouncerInteractor,
-                        mUdfpsOverlayInteractor) {
+                        mUdfpsOverlayInteractor,
+                        mock(UserTracker.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -216,14 +219,34 @@
     }
 
     @Test
-    public void dismissWithAction_AfterKeyguardGoneSetToFalse() {
+    public void dismissWithAction_AfterKeyguardGoneSetToFalse_DismissAction_Set() {
         OnDismissAction action = () -> false;
         Runnable cancelAction = () -> {
         };
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+
         mStatusBarKeyguardViewManager.dismissWithAction(
                 action, cancelAction, false /* afterKeyguardGone */);
+
         verify(mPrimaryBouncerInteractor).setDismissAction(eq(action), eq(cancelAction));
         verify(mPrimaryBouncerInteractor).show(eq(true));
+        assertNull(mStatusBarKeyguardViewManager.mAfterKeyguardGoneAction);
+        assertNull(mStatusBarKeyguardViewManager.mKeyguardGoneCancelAction);
+    }
+
+    @Test
+    public void dismissWithAction_AfterKeyguardGoneSetToFalse_DismissAction_Called() {
+        OnDismissAction action = mock(OnDismissAction.class);
+        Runnable cancelAction = () -> {
+        };
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(false);
+
+        mStatusBarKeyguardViewManager.dismissWithAction(
+                action, cancelAction, false /* afterKeyguardGone */);
+
+        verify(action).onDismiss();
+        assertNull(mStatusBarKeyguardViewManager.mAfterKeyguardGoneAction);
+        assertNull(mStatusBarKeyguardViewManager.mKeyguardGoneCancelAction);
     }
 
     @Test
@@ -680,7 +703,8 @@
                         mPrimaryBouncerInteractor,
                         mBouncerView,
                         mAlternateBouncerInteractor,
-                        mUdfpsOverlayInteractor) {
+                        mUdfpsOverlayInteractor,
+                        mock(UserTracker.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 746c92e..02c459b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -16,12 +16,10 @@
 
 package com.android.systemui.statusbar.phone
 
-import android.animation.Animator
 import android.os.Handler
 import android.os.PowerManager
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
-import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.SysuiTestCase
@@ -39,10 +37,8 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.Mockito.anyLong
 import org.mockito.Mockito.never
-import org.mockito.Mockito.spy
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
@@ -111,27 +107,6 @@
         controller.onStartedWakingUp()
     }
 
-    @Test
-    fun testAnimClearsEndListener() {
-        val keyguardView = View(context)
-        val animator = spy(keyguardView.animate())
-        val keyguardSpy = spy(keyguardView)
-        Mockito.`when`(keyguardSpy.animate()).thenReturn(animator)
-        val listener = ArgumentCaptor.forClass(Animator.AnimatorListener::class.java)
-        val endAction = ArgumentCaptor.forClass(Runnable::class.java)
-        controller.animateInKeyguard(keyguardSpy, Runnable {})
-        Mockito.verify(animator).setListener(listener.capture())
-        Mockito.verify(animator).withEndAction(endAction.capture())
-
-        // Verify that the listener is cleared if we cancel it.
-        listener.value.onAnimationCancel(null)
-        Mockito.verify(animator).setListener(null)
-
-        // Verify that the listener is also cleared if the end action is triggered.
-        endAction.value.run()
-        verify(animator, times(2)).setListener(null)
-    }
-
     /**
      * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal
      * animation to start. If the device is woken up during the screen off, we should *never* do
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index ddb7f4d..01bec87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -32,9 +32,8 @@
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
@@ -69,14 +68,8 @@
                 FakeConnectivityRepository(),
             )
 
-        val subscriptionIdsFlow =
-            interactor.filteredSubscriptions
-                .map { subs -> subs.map { it.subscriptionId } }
-                .stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf())
-
         underTest =
             MobileIconsViewModel(
-                subscriptionIdsFlow,
                 logger,
                 verboseLogger,
                 interactor,
@@ -90,6 +83,32 @@
     }
 
     @Test
+    fun subscriptionIdsFlow_matchesInteractor() =
+        testScope.runTest {
+            var latest: List<Int>? = null
+            val job = underTest.subscriptionIdsFlow.onEach { latest = it }.launchIn(this)
+
+            interactor.filteredSubscriptions.value =
+                listOf(
+                    SubscriptionModel(subscriptionId = 1, isOpportunistic = false),
+                )
+            assertThat(latest).isEqualTo(listOf(1))
+
+            interactor.filteredSubscriptions.value =
+                listOf(
+                    SubscriptionModel(subscriptionId = 2, isOpportunistic = false),
+                    SubscriptionModel(subscriptionId = 5, isOpportunistic = true),
+                    SubscriptionModel(subscriptionId = 7, isOpportunistic = true),
+                )
+            assertThat(latest).isEqualTo(listOf(2, 5, 7))
+
+            interactor.filteredSubscriptions.value = emptyList()
+            assertThat(latest).isEmpty()
+
+            job.cancel()
+        }
+
+    @Test
     fun `caching - mobile icon view model is reused for same sub id`() =
         testScope.runTest {
             val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 1eee08c..91c88ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -21,6 +21,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
 
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
@@ -167,8 +168,10 @@
         mBatteryController.setPowerSaveMode(false, mView);
 
         StaticInOrder inOrder = inOrder(staticMockMarker(BatterySaverUtils.class));
-        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true));
-        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true));
+        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true,
+                SAVER_ENABLED_QS));
+        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true,
+                SAVER_ENABLED_QS));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 01e94ba..391c8ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -62,7 +62,7 @@
 import android.window.WindowOnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
-import androidx.core.animation.AnimatorTestRule2;
+import androidx.core.animation.AnimatorTestRule;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
@@ -110,7 +110,7 @@
     private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
 
     @ClassRule
-    public static AnimatorTestRule2 mAnimatorTestRule = new AnimatorTestRule2();
+    public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
 
     @Before
     public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
new file mode 100644
index 0000000..9cf3e44
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import static android.media.AudioManager.CSD_WARNING_DOSE_REACHED_1X;
+import static android.media.AudioManager.CSD_WARNING_DOSE_REPEATED_5X;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.media.AudioManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class CsdWarningDialogTest extends SysuiTestCase {
+
+    private NotificationManager mNotificationManager;
+    private AudioManager mAudioManager;
+
+    @Before
+    public void setup() {
+        mNotificationManager = mock(NotificationManager.class);
+        getContext().addMockSystemService(NotificationManager.class, mNotificationManager);
+
+        mAudioManager = mock(AudioManager.class);
+        getContext().addMockSystemService(AudioManager.class, mAudioManager);
+    }
+
+    @Test
+    public void create1XCsdDialogAndWait_sendsNotification() {
+        FakeExecutor executor =  new FakeExecutor(new FakeSystemClock());
+        // instantiate directly instead of via factory; we don't want executor to be @Background
+        CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext,
+                mAudioManager, mNotificationManager, executor, null);
+
+        dialog.show();
+        executor.advanceClockToLast();
+        executor.runAllReady();
+        dialog.dismiss();
+
+        verify(mNotificationManager).notify(
+                eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class));
+    }
+
+    @Test
+    public void create5XCsdDiSalogAndWait_willNotSendNotification() {
+        FakeExecutor executor =  new FakeExecutor(new FakeSystemClock());
+        CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
+                mAudioManager, mNotificationManager, executor, null);
+
+        dialog.show();
+        executor.advanceClockToLast();
+        executor.runAllReady();
+        dialog.dismiss();
+
+        verify(mNotificationManager, never()).notify(
+                eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index d419095..eb26888 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -102,6 +102,15 @@
     InteractionJankMonitor mInteractionJankMonitor;
     @Mock
     private DumpManager mDumpManager;
+    @Mock CsdWarningDialog mCsdWarningDialog;
+
+    private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
+            new CsdWarningDialog.Factory() {
+        @Override
+        public CsdWarningDialog create(int warningType, Runnable onCleanup) {
+            return mCsdWarningDialog;
+        }
+    };
 
     @Before
     public void setup() throws Exception {
@@ -124,6 +133,7 @@
                 mInteractionJankMonitor,
                 mDeviceConfigProxy,
                 mExecutor,
+                mCsdWarningDialogFactory,
                 mDumpManager
             );
         mDialog.init(0, null);
@@ -352,6 +362,14 @@
         mDialog.getDialogView().animate().cancel();
     }
 
+    @Test
+    public void showCsdWarning_dialogShown() {
+        mDialog.showCsdWarningH(AudioManager.CSD_WARNING_DOSE_REACHED_1X,
+                CsdWarningDialog.NO_ACTION_TIMEOUT_MS);
+
+        verify(mCsdWarningDialog).show();
+    }
+
 /*
     @Test
     public void testContentDescriptions() {
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
new file mode 100644
index 0000000..738f09d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
+
+    override val isAuthenticated = MutableStateFlow(false)
+    override val canRunFaceAuth = MutableStateFlow(false)
+    private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null)
+    override val authenticationStatus: Flow<AuthenticationStatus> =
+        _authenticationStatus.filterNotNull()
+    fun setAuthenticationStatus(status: AuthenticationStatus) {
+        _authenticationStatus.value = status
+    }
+    private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
+    override val detectionStatus: Flow<DetectionStatus>
+        get() = _detectionStatus.filterNotNull()
+    fun setDetectionStatus(status: DetectionStatus) {
+        _detectionStatus.value = status
+    }
+    override val isLockedOut = MutableStateFlow(false)
+    private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
+    val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
+        _runningAuthRequest.asStateFlow()
+
+    private val _isAuthRunning = MutableStateFlow(false)
+    override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
+
+    override val isBypassEnabled = MutableStateFlow(false)
+
+    override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+        _runningAuthRequest.value = uiEvent to fallbackToDetection
+        _isAuthRunning.value = true
+    }
+
+    override fun cancel() {
+        _isAuthRunning.value = false
+        _runningAuthRequest.value = null
+    }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt
index 0b019d1..3041888 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt
@@ -35,7 +35,7 @@
 
     private var inProgress = false
 
-    private var processedProgress: Float = 0.0f
+    private var processedProgress: Float = 1.0f
         set(newProgress) {
             if (inProgress) {
                 logCounter({ "$TAG#filtered_progress" }, newProgress)
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
index cbca3f0..a41d0e5 100644
--- a/packages/overlays/Android.mk
+++ b/packages/overlays/Android.mk
@@ -32,6 +32,7 @@
 	NavigationBarModeGesturalOverlayWideBack \
 	NavigationBarModeGesturalOverlayExtraWideBack \
 	TransparentNavigationBarOverlay \
+	NotesRoleEnabledOverlay \
 	preinstalled-packages-platform-overlays.xml
 
 include $(BUILD_PHONY_PACKAGE)
diff --git a/core/tests/expresslog/Android.bp b/packages/overlays/NotesRoleEnabledOverlay/Android.bp
similarity index 64%
rename from core/tests/expresslog/Android.bp
rename to packages/overlays/NotesRoleEnabledOverlay/Android.bp
index cab49a7..68ebd96 100644
--- a/core/tests/expresslog/Android.bp
+++ b/packages/overlays/NotesRoleEnabledOverlay/Android.bp
@@ -1,16 +1,18 @@
-// Copyright (C) 2023 The Android Open Source Project
+//
+//  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
+//     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 {
     // See: http://go/android-license-faq
@@ -21,27 +23,8 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_test {
-    name: "ExpressLogTests",
-
-    srcs: [
-        "src/**/*.java",
-    ],
-
-    static_libs: [
-        "androidx.test.rules",
-        "modules-utils-build",
-    ],
-
-    libs: [
-        "android.test.base",
-        "android.test.runner",
-    ],
-
-    platform_apis: true,
-    test_suites: [
-        "general-tests",
-    ],
-
-    certificate: "platform",
+runtime_resource_overlay {
+    name: "NotesRoleEnabledOverlay",
+    theme: "NotesRoleEnabled",
+    product_specific: true,
 }
diff --git a/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml b/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..c01178d
--- /dev/null
+++ b/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<!--
+/**
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.role.notes.enabled"
+        android:versionCode="1"
+        android:versionName="1.0">
+    <overlay android:targetPackage="android"
+        android:priority="1"/>
+
+    <application android:label="@string/notes_role_enabled_overlay_title" android:hasCode="false"/>
+</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml b/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml
new file mode 100644
index 0000000..f27f5f4
--- /dev/null
+++ b/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+    <!-- Whether the default notes role should be enabled. In builds without 
+    device-specific overlays, this can be controlled in developer options. -->
+     <bool name="config_enableDefaultNotes">true</bool>
+
+    <!-- Whether the default notes role for work profile should be enabled. 
+    In builds without device-specific overlays, this can be controlled in 
+    developer options. -->
+    <bool name="config_enableDefaultNotesForWorkProfile">true</bool>
+</resources>
diff --git a/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml b/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml
new file mode 100644
index 0000000..3edbb57
--- /dev/null
+++ b/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Name of overlay [CHAR LIMIT=64] -->
+    <string name="notes_role_enabled_overlay_title" translatable="false">Notes Role enabled</string>
+</resources>
\ No newline at end of file
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 4702734..21d0979 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -402,5 +402,7 @@
     // Package: android
     NOTE_ALL_MANAGED_SUBSCRIPTIONS_AND_MANAGED_PROFILE_OFF = 1006;
 
+    // Notify the user that audio was lowered based on Calculated Sound Dose (CSD)
+    NOTE_CSD_LOWER_AUDIO = 1007;
   }
 }
diff --git a/services/Android.bp b/services/Android.bp
index 6e6c553..b0a0e5e 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -112,6 +112,7 @@
         ":services.searchui-sources",
         ":services.selectiontoolbar-sources",
         ":services.smartspace-sources",
+        ":services.soundtrigger-sources",
         ":services.systemcaptions-sources",
         ":services.translation-sources",
         ":services.texttospeech-sources",
@@ -169,6 +170,7 @@
         "services.searchui",
         "services.selectiontoolbar",
         "services.smartspace",
+        "services.soundtrigger",
         "services.systemcaptions",
         "services.translation",
         "services.texttospeech",
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 51325e7..0bdb0c8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -112,6 +112,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.Display;
 import android.view.IWindow;
 import android.view.InputDevice;
@@ -160,6 +161,7 @@
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.utils.Slogf;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.settingslib.RestrictedLockUtils;
@@ -301,7 +303,23 @@
     private final List<SendWindowStateChangedEventRunnable> mSendWindowStateChangedEventRunnables =
             new ArrayList<>();
 
-    private int mCurrentUserId = UserHandle.USER_SYSTEM;
+    @GuardedBy("mLock")
+    private @UserIdInt int mCurrentUserId = UserHandle.USER_SYSTEM;
+
+    // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation:
+    // when the UiAutomation is set in a visible background user, mCurrentUserId points to that user
+    // and mRealCurrentUserId points to the "real" current user; otherwise, mRealCurrentUserId
+    // is set as UserHandle.USER_CURRENT.
+    @GuardedBy("mLock")
+    private @UserIdInt int mRealCurrentUserId = UserHandle.USER_CURRENT;
+
+    // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+    // purposes - in the long term, the whole service should be refactored so it handles "visible"
+    // users, not current user. Notice that because this is temporary, it's not trying to optimize
+    // performance / utilization (for example, it's not using an IntArray)
+    @GuardedBy("mLock")
+    @Nullable // only set when device supports visible background users
+    private final SparseBooleanArray mVisibleBgUserIds;
 
     //TODO: Remove this hack
     private boolean mInitialized;
@@ -316,6 +334,7 @@
     private SparseArray<SurfaceControl> mA11yOverlayLayers = new SparseArray<>();
 
     private final FlashNotificationsController mFlashNotificationsController;
+    private final UserManagerInternal mUmi;
 
     private AccessibilityUserState getCurrentUserStateLocked() {
         return getUserStateLocked(mCurrentUserId);
@@ -445,6 +464,10 @@
             mHasInputFilter = true;
         }
         mFlashNotificationsController = new FlashNotificationsController(mContext);
+        mUmi = LocalServices.getService(UserManagerInternal.class);
+        // TODO(b/255426725): not used on tests
+        mVisibleBgUserIds = null;
+
         init();
     }
 
@@ -477,6 +500,15 @@
         mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext, mMainHandler,
                 mUiAutomationManager, this);
         mFlashNotificationsController = new FlashNotificationsController(mContext);
+        mUmi = LocalServices.getService(UserManagerInternal.class);
+
+        if (UserManager.isVisibleBackgroundUsersEnabled()) {
+            mVisibleBgUserIds = new SparseBooleanArray();
+            mUmi.addUserVisibilityListener((u, v) -> onUserVisibilityChanged(u, v));
+        } else {
+            mVisibleBgUserIds = null;
+        }
+
         init();
     }
 
@@ -493,6 +525,12 @@
         return mCurrentUserId;
     }
 
+    @GuardedBy("mLock")
+    @Override
+    public SparseBooleanArray getVisibleUserIdsLocked() {
+        return mVisibleBgUserIds;
+    }
+
     @Override
     public boolean isAccessibilityButtonShown() {
         return mIsAccessibilityButtonShown;
@@ -1362,6 +1400,7 @@
     public void registerUiTestAutomationService(IBinder owner,
             IAccessibilityServiceClient serviceClient,
             AccessibilityServiceInfo accessibilityServiceInfo,
+            int userId,
             int flags) {
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".registerUiTestAutomationService",
@@ -1374,6 +1413,7 @@
                 FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE);
 
         synchronized (mLock) {
+            changeCurrentUserForTestAutomationIfNeededLocked(userId);
             mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient,
                     mContext, accessibilityServiceInfo, sIdCounter++, mMainHandler,
                     mSecurityPolicy, this, getTraceManager(), mWindowManagerService,
@@ -1390,9 +1430,49 @@
         }
         synchronized (mLock) {
             mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient);
+            restoreCurrentUserAfterTestAutomationIfNeededLocked();
         }
     }
 
+    // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+    @GuardedBy("mLock")
+    private void changeCurrentUserForTestAutomationIfNeededLocked(@UserIdInt int userId) {
+        if (mVisibleBgUserIds == null) {
+            Slogf.d(LOG_TAG, "changeCurrentUserForTestAutomationIfNeededLocked(%d): ignoring "
+                    + "because device doesn't support visible background users", userId);
+            return;
+        }
+        if (!mVisibleBgUserIds.get(userId)) {
+            Slogf.wtf(LOG_TAG, "Cannot change current user to %d as it's not visible "
+                    + "(mVisibleUsers=%s)", userId, mVisibleBgUserIds);
+            return;
+        }
+        if (mCurrentUserId == userId) {
+            Slogf.w(LOG_TAG, "NOT changing current user for test automation purposes as it is "
+                    + "already %d", mCurrentUserId);
+            return;
+        }
+        Slogf.i(LOG_TAG, "Changing current user from %d to %d for test automation purposes",
+                mCurrentUserId, userId);
+        mRealCurrentUserId = mCurrentUserId;
+        switchUser(userId);
+    }
+
+    // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+    @GuardedBy("mLock")
+    private void restoreCurrentUserAfterTestAutomationIfNeededLocked() {
+        if (mVisibleBgUserIds == null) {
+            Slogf.d(LOG_TAG, "restoreCurrentUserForTestAutomationIfNeededLocked(): ignoring "
+                    + "because device doesn't support visible background users");
+            return;
+        }
+        Slogf.i(LOG_TAG, "Restoring current user to %d after using %d for test automation purposes",
+                mRealCurrentUserId, mCurrentUserId);
+        int currentUserId = mRealCurrentUserId;
+        mRealCurrentUserId = UserHandle.USER_CURRENT;
+        switchUser(currentUserId);
+    }
+
     @Override
     public IBinder getWindowToken(int windowId, int userId) {
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
@@ -2291,8 +2371,7 @@
     private void updateServicesLocked(AccessibilityUserState userState) {
         Map<ComponentName, AccessibilityServiceConnection> componentNameToServiceMap =
                 userState.mComponentNameToServiceMap;
-        boolean isUnlockingOrUnlocked = LocalServices.getService(UserManagerInternal.class)
-                    .isUserUnlockingOrUnlocked(userState.mUserId);
+        boolean isUnlockingOrUnlocked = mUmi.isUserUnlockingOrUnlocked(userState.mUserId);
 
         for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) {
             AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i);
@@ -2593,6 +2672,19 @@
         }
     }
 
+    private void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
+        if (DEBUG) {
+            Slogf.d(LOG_TAG, "onUserVisibilityChanged(): %d => %b", userId, visible);
+        }
+        synchronized (mLock) {
+            if (visible) {
+                mVisibleBgUserIds.put(userId, visible);
+            } else {
+                mVisibleBgUserIds.delete(userId);
+            }
+        }
+    }
+
     /**
      * Called when any property of the user state has changed.
      *
@@ -4025,7 +4117,16 @@
             pw.println("ACCESSIBILITY MANAGER (dumpsys accessibility)");
             pw.println();
             pw.append("currentUserId=").append(String.valueOf(mCurrentUserId));
+            if (mRealCurrentUserId != UserHandle.USER_CURRENT
+                    && mCurrentUserId != mRealCurrentUserId) {
+                pw.append(" (set for UiAutomation purposes; \"real\" current user is ")
+                        .append(String.valueOf(mRealCurrentUserId)).append(")");
+            }
             pw.println();
+            if (mVisibleBgUserIds != null) {
+                pw.append("visibleBgUserIds=").append(mVisibleBgUserIds.toString());
+                pw.println();
+            }
             pw.append("hasWindowMagnificationConnection=").append(
                     String.valueOf(getWindowMagnificationMgr().isConnected()));
             pw.println();
@@ -4052,6 +4153,7 @@
             }
             pw.println();
             mProxyManager.dump(fd, pw, args);
+            mA11yDisplayListener.dump(fd, pw, args);
         }
     }
 
@@ -4437,6 +4539,20 @@
             /* do nothing */
         }
 
+        void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            pw.println("Accessibility Display Listener:");
+            pw.println("    SystemUI uid: " + mSystemUiUid);
+            int size = mDisplaysList.size();
+            pw.printf("    %d valid display%s: ", size, (size == 1 ? "" : "s"));
+            for (int i = 0; i < size; i++) {
+                pw.print(mDisplaysList.get(i).getDisplayId());
+                if (i < size - 1) {
+                    pw.print(", ");
+                }
+            }
+            pw.println();
+        }
+
         private boolean isValidDisplay(@Nullable Display display) {
             if (display == null || display.getType() == Display.TYPE_OVERLAY) {
                 return false;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index c37ea50..8865623 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -39,6 +39,7 @@
 import android.os.UserManager;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.inputmethod.InputMethodInfo;
 
@@ -88,6 +89,12 @@
          */
         int getCurrentUserIdLocked();
         // TODO: Should include resolveProfileParentLocked, but that was already in SecurityPolicy
+
+        // TODO(b/255426725): temporary hack; see comment on A11YMS.mVisibleBgUserIds
+        /**
+         * Returns the {@link android.os.UserManager#getVisibleUsers() visible users}.
+         */
+        @Nullable SparseBooleanArray getVisibleUserIdsLocked();
     }
 
     private final Context mContext;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index baed181..a8a5365 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -51,6 +51,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.accessibility.AccessibilitySecurityPolicy.AccessibilityUserManager;
+import com.android.server.utils.Slogf;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
@@ -59,6 +60,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * This class provides APIs for accessibility manager to manage {@link AccessibilityWindowInfo}s and
@@ -67,6 +69,7 @@
 public class AccessibilityWindowManager {
     private static final String LOG_TAG = "AccessibilityWindowManager";
     private static final boolean DEBUG = false;
+    private static final boolean VERBOSE = false;
 
     private static int sNextWindowId;
 
@@ -209,6 +212,9 @@
          * Constructor for DisplayWindowsObserver.
          */
         DisplayWindowsObserver(int displayId) {
+            if (DEBUG) {
+                Slogf.d(LOG_TAG, "Creating DisplayWindowsObserver for displayId %d", displayId);
+            }
             mDisplayId = displayId;
         }
 
@@ -430,12 +436,27 @@
             synchronized (mLock) {
                 updateWindowsByWindowAttributesLocked(windows);
                 if (DEBUG) {
-                    Slog.i(LOG_TAG, "Display Id = " + mDisplayId);
-                    Slog.i(LOG_TAG, "Windows changed: " + windows);
+                    Slogf.i(LOG_TAG, "mDisplayId=%d, topFocusedDisplayId=%d, currentUserId=%d, "
+                            + "visibleBgUsers=%s", mDisplayId, topFocusedDisplayId,
+                            mAccessibilityUserManager.getCurrentUserIdLocked(),
+                            mAccessibilityUserManager.getVisibleUserIdsLocked());
+                    if (VERBOSE) {
+                        Slogf.i(LOG_TAG, "%d windows changed: %s ", windows.size(), windows);
+                    } else {
+                        List<String> windowsInfo = windows.stream()
+                                .map(w -> "{displayId=" + w.displayId + ", title=" + w.title + "}")
+                                .collect(Collectors.toList());
+                        Slogf.i(LOG_TAG, "%d windows changed: %s", windows.size(), windowsInfo);
+                    }
                 }
                 if (shouldUpdateWindowsLocked(forceSend, windows)) {
                     mTopFocusedDisplayId = topFocusedDisplayId;
                     mTopFocusedWindowToken = topFocusedWindowToken;
+                    if (DEBUG) {
+                        Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): updating windows for "
+                                + "display %d and token %s",
+                                topFocusedDisplayId, topFocusedWindowToken);
+                    }
                     cacheWindows(windows);
                     // Lets the policy update the focused and active windows.
                     updateWindowsLocked(mAccessibilityUserManager.getCurrentUserIdLocked(),
@@ -443,6 +464,11 @@
                     // Someone may be waiting for the windows - advertise it.
                     mLock.notifyAll();
                 }
+                else if (DEBUG) {
+                    Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): NOT updating windows for "
+                            + "display %d and token %s",
+                            topFocusedDisplayId, topFocusedWindowToken);
+                }
             }
         }
 
@@ -472,6 +498,12 @@
             }
 
             final int windowCount = windows.size();
+            if (VERBOSE) {
+                Slogf.v(LOG_TAG,
+                        "shouldUpdateWindowsLocked(): mDisplayId=%d, windowCount=%d, "
+                        + "mCachedWindowInfos.size()=%d, windows.size()=%d", mDisplayId,
+                        windowCount, mCachedWindowInfos.size(), windows.size());
+            }
             // We computed the windows and if they changed notify the client.
             if (mCachedWindowInfos.size() != windowCount) {
                 // Different size means something changed.
@@ -1274,7 +1306,7 @@
      */
     @Nullable
     public RemoteAccessibilityConnection getConnectionLocked(int userId, int windowId) {
-        if (DEBUG) {
+        if (VERBOSE) {
             Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);
         }
         RemoteAccessibilityConnection connection = mGlobalInteractionConnections.get(windowId);
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 8d039fc..2a964b8 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2073,15 +2073,14 @@
     @Override
     public void onSaveRequestSuccess(@NonNull String servicePackageName,
             @Nullable IntentSender intentSender) {
-        // Log onSaveRequest result.
-        mSaveEventLogger.maybeSetIsSaved(true);
-        final long saveRequestFinishTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime;
-        mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
-        mSaveEventLogger.logAndEndEvent();
-
         synchronized (mLock) {
             mSessionFlags.mShowingSaveUi = false;
-
+            // Log onSaveRequest result.
+            mSaveEventLogger.maybeSetIsSaved(true);
+            final long saveRequestFinishTimestamp =
+                SystemClock.elapsedRealtime() - mLatencyBaseTime;
+            mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
+            mSaveEventLogger.logAndEndEvent();
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: "
                         + id + " destroyed");
@@ -2108,14 +2107,13 @@
             @NonNull String servicePackageName) {
         boolean showMessage = !TextUtils.isEmpty(message);
 
-        // Log onSaveRequest result.
-        final long saveRequestFinishTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime;
-        mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
-        mSaveEventLogger.logAndEndEvent();
-
         synchronized (mLock) {
             mSessionFlags.mShowingSaveUi = false;
-
+            // Log onSaveRequest result.
+            final long saveRequestFinishTimestamp =
+                SystemClock.elapsedRealtime() - mLatencyBaseTime;
+            mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
+            mSaveEventLogger.logAndEndEvent();
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: "
                         + id + " destroyed");
@@ -2228,8 +2226,8 @@
     // AutoFillUiCallback
     @Override
     public void save() {
-        mSaveEventLogger.maybeSetSaveButtonClicked(true);
         synchronized (mLock) {
+            mSaveEventLogger.maybeSetSaveButtonClicked(true);
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#save() rejected - session: "
                         + id + " destroyed");
@@ -2247,10 +2245,9 @@
     // AutoFillUiCallback
     @Override
     public void cancelSave() {
-        mSaveEventLogger.maybeSetDialogDismissed(true);
         synchronized (mLock) {
             mSessionFlags.mShowingSaveUi = false;
-
+            mSaveEventLogger.maybeSetDialogDismissed(true);
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#cancelSave() rejected - session: "
                         + id + " destroyed");
diff --git a/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
index 1990fe2..98aebdd 100644
--- a/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
+++ b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
@@ -77,4 +77,19 @@
                 /* name= */ "full_backup_utils_route_buffer_size_bytes",
                 /* defaultValue= */ 32 * 1024); // 32 KB
     }
+
+    /**
+     * Retrieves the value of the flag
+     * "unified_restore_continue_after_transport_failure_in_kv_restore".
+     * If true, Unified restore task will continue to next package if key-value restore of a
+     * package fails due to Transport-level failure. See b/128499560 for more context.
+     */
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+    public static boolean getUnifiedRestoreContinueAfterTransportFailureInKvRestore() {
+        return DeviceConfig.getBoolean(
+                NAMESPACE,
+                /* name= */
+                "unified_restore_continue_after_transport_failure_in_kv_restore",
+                /* defaultValue= */ true);
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 18e28de..1656b6f 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -57,6 +57,7 @@
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.BackupAndRestoreFeatureFlags;
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.BackupUtils;
 import com.android.server.backup.OperationStorage;
@@ -168,11 +169,13 @@
     private final BackupEligibilityRules mBackupEligibilityRules;
 
     @VisibleForTesting
-    PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) {
+    PerformUnifiedRestoreTask(
+            UserBackupManagerService backupManagerService,
+            TransportConnection transportConnection) {
         mListener = null;
         mAgentTimeoutParameters = null;
         mOperationStorage = null;
-        mTransportConnection = null;
+        mTransportConnection = transportConnection;
         mTransportManager = null;
         mEphemeralOpToken = 0;
         mUserId = 0;
@@ -731,13 +734,18 @@
                             ParcelFileDescriptor.MODE_TRUNCATE);
 
             if (transport.getRestoreData(stage) != BackupTransport.TRANSPORT_OK) {
-                // Transport-level failure, so we wind everything up and
-                // terminate the restore operation.
+                // Transport-level failure. This failure could be specific to package currently in
+                // restore.
                 Slog.e(TAG, "Error getting restore data for " + packageName);
                 EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
                 stage.close();
                 downloadFile.delete();
-                executeNextState(UnifiedRestoreState.FINAL);
+                UnifiedRestoreState nextState =
+                        BackupAndRestoreFeatureFlags
+                                .getUnifiedRestoreContinueAfterTransportFailureInKvRestore()
+                                ? UnifiedRestoreState.RUNNING_QUEUE
+                                : UnifiedRestoreState.FINAL;
+                executeNextState(nextState);
                 return;
             }
 
@@ -1358,6 +1366,7 @@
         executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
     }
 
+    @VisibleForTesting
     void executeNextState(UnifiedRestoreState nextState) {
         if (MORE_DEBUG) {
             Slog.i(TAG, " => executing next step on "
@@ -1369,6 +1378,26 @@
         backupManagerService.getBackupHandler().sendMessage(msg);
     }
 
+    @VisibleForTesting
+    UnifiedRestoreState getCurrentUnifiedRestoreStateForTesting() {
+        return mState;
+    }
+
+    @VisibleForTesting
+    void setCurrentUnifiedRestoreStateForTesting(UnifiedRestoreState state) {
+        mState = state;
+    }
+
+    @VisibleForTesting
+    void setStateDirForTesting(File stateDir) {
+        mStateDir = stateDir;
+    }
+
+    @VisibleForTesting
+    void initiateOneRestoreForTesting(PackageInfo app, long appVersionCode) {
+        initiateOneRestore(app, appVersionCode);
+    }
+
     // restore observer support
     void sendStartRestore(int numPackages) {
         if (mObserver != null) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index c8caab9..cfdf3ac 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -174,7 +174,6 @@
         "android.hardware.configstore-V1.1-java",
         "android.hardware.ir-V1-java",
         "android.hardware.rebootescrow-V1-java",
-        "android.hardware.soundtrigger-V2.3-java",
         "android.hardware.power.stats-V2-java",
         "android.hardware.power-V4-java",
         "android.hidl.manager-V1.2-java",
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 3ecf933..8fc30e4 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -80,7 +80,7 @@
 import android.util.apk.ApkSigningBlockUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Histogram;
+import com.android.modules.expresslog.Histogram;
 import com.android.internal.os.IBinaryTransparencyService;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.pm.ApexManager;
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 92889c0..d256aea 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -426,7 +426,7 @@
                                 }
                                 int impact = registeredObserver.onHealthCheckFailed(
                                         versionedPackage, failureReason, mitigationCount);
-                                if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+                                if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
                                         && impact < currentObserverImpact) {
                                     currentObserverToNotify = registeredObserver;
                                     currentObserverImpact = impact;
@@ -466,7 +466,7 @@
             if (registeredObserver != null) {
                 int impact = registeredObserver.onHealthCheckFailed(
                         failingPackage, failureReason, 1);
-                if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+                if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
                         && impact < currentObserverImpact) {
                     currentObserverToNotify = registeredObserver;
                     currentObserverImpact = impact;
@@ -494,7 +494,7 @@
                     PackageHealthObserver registeredObserver = observer.registeredObserver;
                     if (registeredObserver != null) {
                         int impact = registeredObserver.onBootLoop(mitigationCount);
-                        if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+                        if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
                                 && impact < currentObserverImpact) {
                             currentObserverToNotify = registeredObserver;
                             currentObserverImpact = impact;
@@ -576,19 +576,23 @@
 
     /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
     @Retention(SOURCE)
-    @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE,
-                     PackageHealthObserverImpact.USER_IMPACT_LOW,
-                     PackageHealthObserverImpact.USER_IMPACT_MEDIUM,
-                     PackageHealthObserverImpact.USER_IMPACT_HIGH})
+    @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_100})
     public @interface PackageHealthObserverImpact {
         /** No action to take. */
-        int USER_IMPACT_NONE = 0;
+        int USER_IMPACT_LEVEL_0 = 0;
         /* Action has low user impact, user of a device will barely notice. */
-        int USER_IMPACT_LOW = 1;
-        /* Action has medium user impact, user of a device will likely notice. */
-        int USER_IMPACT_MEDIUM = 3;
+        int USER_IMPACT_LEVEL_10 = 10;
+        /* Actions having medium user impact, user of a device will likely notice. */
+        int USER_IMPACT_LEVEL_30 = 30;
+        int USER_IMPACT_LEVEL_50 = 50;
+        int USER_IMPACT_LEVEL_70 = 70;
         /* Action has high user impact, a last resort, user of a device will be very frustrated. */
-        int USER_IMPACT_HIGH = 5;
+        int USER_IMPACT_LEVEL_100 = 100;
     }
 
     /** Register instances of this interface to receive notifications on package failure. */
@@ -633,7 +637,7 @@
          *                        boot loop (including this time).
          */
         default @PackageHealthObserverImpact int onBootLoop(int mitigationCount) {
-            return PackageHealthObserverImpact.USER_IMPACT_NONE;
+            return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
         }
 
         /**
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 3de65f9..6e2e5f7 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -107,7 +107,7 @@
     static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
             "namespace_to_package_mapping";
     @VisibleForTesting
-    static final long FACTORY_RESET_THROTTLE_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
+    static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 10;
 
     private static final String NAME = "rescue-party-observer";
 
@@ -117,6 +117,8 @@
             "persist.device_config.configuration.disable_rescue_party";
     private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
             "persist.device_config.configuration.disable_rescue_party_factory_reset";
+    private static final String PROP_THROTTLE_DURATION_MIN_FLAG =
+            "persist.device_config.configuration.rescue_party_throttle_duration_min";
 
     private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
             | ApplicationInfo.FLAG_SYSTEM;
@@ -489,13 +491,14 @@
         switch(rescueLevel) {
             case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
             case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
-                return PackageHealthObserverImpact.USER_IMPACT_LOW;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
             case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
             case LEVEL_WARM_REBOOT:
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
             case LEVEL_FACTORY_RESET:
-                return PackageHealthObserverImpact.USER_IMPACT_HIGH;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
             default:
-                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
         }
     }
 
@@ -633,7 +636,7 @@
                 return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
                         mayPerformReboot(failedPackage)));
             } else {
-                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
             }
         }
 
@@ -677,7 +680,7 @@
         @Override
         public int onBootLoop(int mitigationCount) {
             if (isDisabled()) {
-                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
             }
             return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
         }
@@ -721,7 +724,9 @@
         private boolean shouldThrottleReboot() {
             Long lastResetTime = SystemProperties.getLong(PROP_LAST_FACTORY_RESET_TIME_MS, 0);
             long now = System.currentTimeMillis();
-            return now < lastResetTime + FACTORY_RESET_THROTTLE_DURATION_MS;
+            long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG,
+                    DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN);
+            return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin);
         }
 
         private boolean isPersistentSystemApp(@NonNull String packageName) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/core/java/com/android/server/SoundTriggerInternal.java
similarity index 97%
rename from services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
rename to services/core/java/com/android/server/SoundTriggerInternal.java
index cc398d9..e6c1750 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/core/java/com/android/server/SoundTriggerInternal.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.soundtrigger;
+package com.android.server;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -29,15 +29,13 @@
 import android.media.permission.Identity;
 import android.os.IBinder;
 
-import com.android.server.voiceinteraction.VoiceInteractionManagerService;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.List;
 
 /**
  * Provides a local service for managing voice-related recoginition models. This is primarily used
- * by the {@link VoiceInteractionManagerService}.
+ * by the {@code VoiceInteractionManagerService}.
  */
 public interface SoundTriggerInternal {
     /**
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7b618b1..e248007 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -26,6 +26,17 @@
 import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
 import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
 import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DEPRECATED;
 import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DISABLED;
 import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_OK;
@@ -117,6 +128,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityManagerInternal.ServiceNotificationPolicy;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
@@ -1146,7 +1158,7 @@
                                             } finally {
                                                 /* Will be a no-op if nothing pending */
                                                 mAm.updateOomAdjPendingTargetsLocked(
-                                                        OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+                                                        OOM_ADJ_REASON_START_SERVICE);
                                             }
                                         } else {
                                             unbindServiceLocked(connection);
@@ -1236,8 +1248,7 @@
                             /* ignore - local call */
                         } finally {
                             /* Will be a no-op if nothing pending */
-                            mAm.updateOomAdjPendingTargetsLocked(
-                                    OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+                            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
                         }
                     } else { // Starting a service
                         try {
@@ -1311,7 +1322,7 @@
                 false /* packageFrozen */,
                 true /* enqueueOomAdj */);
         /* Will be a no-op if nothing pending */
-        mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+        mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
         if (error != null) {
             return new ComponentName("!!", error);
         }
@@ -1496,7 +1507,7 @@
                     stopServiceLocked(service, true);
                 }
                 if (size > 0) {
-                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                    mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UID_IDLE);
                 }
             }
         }
@@ -3296,7 +3307,7 @@
 
             Slog.e(TAG_SERVICE, "Short FGS procstate demoted: " + sr);
 
-            mAm.updateOomAdjLocked(sr.app, OomAdjuster.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
+            mAm.updateOomAdjLocked(sr.app, OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
         }
     }
 
@@ -3630,7 +3641,7 @@
                 needOomAdj = true;
                 if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
                         permissionsReviewRequired, packageFrozen, true) != null) {
-                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                    mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
                     return 0;
                 }
             }
@@ -3655,7 +3666,7 @@
                 mAm.enqueueOomAdjTargetLocked(s.app);
             }
             if (needOomAdj) {
-                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
             }
 
             final int packageState = wasStopped
@@ -3787,7 +3798,8 @@
                     }
                 }
 
-                serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false);
+                serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false,
+                        OOM_ADJ_REASON_EXECUTING_SERVICE);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -3878,7 +3890,7 @@
                 }
             }
 
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE);
 
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -3925,7 +3937,8 @@
                     }
                 }
 
-                serviceDoneExecutingLocked(r, inDestroying, false, false);
+                serviceDoneExecutingLocked(r, inDestroying, false, false,
+                        OOM_ADJ_REASON_UNBIND_SERVICE);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -4360,11 +4373,11 @@
     /**
      * Bump the given service record into executing state.
      * @param oomAdjReason The caller requests it to perform the oomAdjUpdate not {@link
-     *         OomAdjuster#OOM_ADJ_REASON_NONE}.
+     *         ActivityManagerInternal#OOM_ADJ_REASON_NONE}.
      * @return {@code true} if it performed oomAdjUpdate.
      */
     private boolean bumpServiceExecutingLocked(
-            ServiceRecord r, boolean fg, String why, @OomAdjuster.OomAdjReason int oomAdjReason) {
+            ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason) {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
                 + why + " of " + r + " in app " + r.app);
         else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
@@ -4416,7 +4429,7 @@
             }
         }
         boolean oomAdjusted = false;
-        if (oomAdjReason != OomAdjuster.OOM_ADJ_REASON_NONE && r.app != null
+        if (oomAdjReason != OOM_ADJ_REASON_NONE && r.app != null
                 && r.app.mState.getCurProcState() > ActivityManager.PROCESS_STATE_SERVICE) {
             // Force an immediate oomAdjUpdate, so the client app could be in the correct process
             // state before doing any service related transactions
@@ -4440,8 +4453,7 @@
                 + " rebind=" + rebind);
         if ((!i.requested || rebind) && i.apps.size() > 0) {
             try {
-                bumpServiceExecutingLocked(r, execInFg, "bind",
-                        OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                bumpServiceExecutingLocked(r, execInFg, "bind", OOM_ADJ_REASON_BIND_SERVICE);
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                     Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding="
                             + i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter);
@@ -4457,13 +4469,15 @@
                 // Keep the executeNesting count accurate.
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+                        OOM_ADJ_REASON_UNBIND_SERVICE);
                 throw e;
             } catch (RemoteException e) {
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r);
                 // Keep the executeNesting count accurate.
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+                        OOM_ADJ_REASON_UNBIND_SERVICE);
                 return false;
             }
         }
@@ -4841,7 +4855,7 @@
             // Ignore, it's been logged and nothing upstack cares.
         } finally {
             /* Will be a no-op if nothing pending */
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
         }
     }
 
@@ -5193,13 +5207,14 @@
 
         final ProcessServiceRecord psr = app.mServices;
         final boolean newService = psr.startService(r);
-        bumpServiceExecutingLocked(r, execInFg, "create", OomAdjuster.OOM_ADJ_REASON_NONE);
+        bumpServiceExecutingLocked(r, execInFg, "create",
+                OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
         mAm.updateLruProcessLocked(app, false, null);
         updateServiceForegroundLocked(psr, /* oomAdj= */ false);
         // Force an immediate oomAdjUpdate, so the client app could be in the correct process state
         // before doing any service related transactions
         mAm.enqueueOomAdjTargetLocked(app);
-        mAm.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+        mAm.updateOomAdjLocked(app, OOM_ADJ_REASON_START_SERVICE);
 
         boolean created = false;
         try {
@@ -5233,7 +5248,8 @@
             if (!created) {
                 // Keep the executeNesting count accurate.
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+                        OOM_ADJ_REASON_STOP_SERVICE);
 
                 // Cleanup.
                 if (newService) {
@@ -5319,7 +5335,8 @@
             mAm.grantImplicitAccess(r.userId, si.intent, si.callingId,
                     UserHandle.getAppId(r.appInfo.uid)
             );
-            bumpServiceExecutingLocked(r, execInFg, "start", OomAdjuster.OOM_ADJ_REASON_NONE);
+            bumpServiceExecutingLocked(r, execInFg, "start",
+                    OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
             if (r.fgRequired && !r.fgWaiting) {
                 if (!r.isForeground) {
                     if (DEBUG_BACKGROUND_CHECK) {
@@ -5345,7 +5362,7 @@
 
         if (!oomAdjusted) {
             mAm.enqueueOomAdjTargetLocked(r.app);
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
         }
         ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args);
         slice.setInlineCountLimit(4);
@@ -5371,10 +5388,11 @@
             // Keep nesting count correct
             final boolean inDestroying = mDestroyingServices.contains(r);
             for (int i = 0, size = args.size(); i < size; i++) {
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, true);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, true,
+                        OOM_ADJ_REASON_STOP_SERVICE);
             }
             /* Will be a no-op if nothing pending */
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
             if (caughtException instanceof TransactionTooLargeException) {
                 throw (TransactionTooLargeException)caughtException;
             }
@@ -5461,7 +5479,7 @@
                 if (ibr.hasBound) {
                     try {
                         oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind",
-                                OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                                OOM_ADJ_REASON_UNBIND_SERVICE);
                         ibr.hasBound = false;
                         ibr.requested = false;
                         r.app.getThread().scheduleUnbindService(r,
@@ -5615,7 +5633,7 @@
                 } else {
                     try {
                         oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
-                                oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                                oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE);
                         mDestroyingServices.add(r);
                         r.destroying = true;
                         r.app.getThread().scheduleStopService(r);
@@ -5637,7 +5655,7 @@
         if (!oomAdjusted) {
             mAm.enqueueOomAdjTargetLocked(r.app);
             if (!enqueueOomAdj) {
-                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
             }
         }
         if (r.bindings.size() > 0) {
@@ -5762,8 +5780,7 @@
             if (s.app != null && s.app.getThread() != null && b.intent.apps.size() == 0
                     && b.intent.hasBound) {
                 try {
-                    bumpServiceExecutingLocked(s, false, "unbind",
-                            OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                    bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE);
                     if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY)
                             && s.app.mState.getSetProcState() <= PROCESS_STATE_HEAVY_WEIGHT) {
                         // If this service's process is not already in the cached list,
@@ -5886,7 +5903,8 @@
                 }
             }
             final long origId = Binder.clearCallingIdentity();
-            serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj);
+            serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj,
+                    OOM_ADJ_REASON_EXECUTING_SERVICE);
             Binder.restoreCallingIdentity(origId);
         } else {
             Slog.w(TAG, "Done executing unknown service from pid "
@@ -5905,11 +5923,11 @@
                 r.tracker.setStarted(false, memFactor, now);
             }
         }
-        serviceDoneExecutingLocked(r, true, true, enqueueOomAdj);
+        serviceDoneExecutingLocked(r, true, true, enqueueOomAdj, OOM_ADJ_REASON_PROCESS_END);
     }
 
     private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
-            boolean finishing, boolean enqueueOomAdj) {
+            boolean finishing, boolean enqueueOomAdj, @OomAdjReason int oomAdjReason) {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "<<< DONE EXECUTING " + r
                 + ": nesting=" + r.executeNesting
                 + ", inDestroying=" + inDestroying + ", app=" + r.app);
@@ -5945,7 +5963,7 @@
                 if (enqueueOomAdj) {
                     mAm.enqueueOomAdjTargetLocked(r.app);
                 } else {
-                    mAm.updateOomAdjLocked(r.app, OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                    mAm.updateOomAdjLocked(r.app, oomAdjReason);
                 }
             }
             r.executeFg = false;
@@ -6015,7 +6033,7 @@
                         bringDownServiceLocked(sr, true);
                     }
                     /* Will be a no-op if nothing pending */
-                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+                    mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
                 }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception in new application when starting service "
@@ -6075,7 +6093,7 @@
             }
         }
         if (needOomAdj) {
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_PROCESS_END);
         }
     }
 
@@ -6146,7 +6164,7 @@
                 bringDownServiceLocked(mTmpCollectionResults.get(i), true);
             }
             if (size > 0) {
-                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_COMPONENT_DISABLED);
             }
             if (fullStop && !mTmpCollectionResults.isEmpty()) {
                 // if we're tearing down the app's entire service state, account for possible
@@ -6273,7 +6291,7 @@
             }
         }
         if (needOomAdj) {
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_REMOVE_TASK);
         }
     }
 
@@ -6444,7 +6462,7 @@
             }
         }
 
-        mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+        mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
 
         if (!allowRestart) {
             psr.stopAllServices();
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index ae5dbe1..44e198b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -213,7 +213,7 @@
     private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false;
     private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000;
 
-    private static final boolean DEFAULT_USE_MODERN_TRIM = false;
+    private static final boolean DEFAULT_USE_MODERN_TRIM = true;
 
     /**
      * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9752ade..b32f8c9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -44,6 +44,14 @@
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.OP_NONE;
 import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
@@ -199,6 +207,7 @@
 import android.app.ActivityManagerInternal.BroadcastEventListener;
 import android.app.ActivityManagerInternal.ForegroundServiceStateListener;
 import android.app.ActivityManagerInternal.MediaProjectionTokenEvent;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.ActivityThread;
 import android.app.AnrController;
@@ -368,7 +377,6 @@
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
 import android.util.Log;
-import android.util.LogWriter;
 import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -1968,7 +1976,7 @@
                 app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
                 addPidLocked(app);
                 updateLruProcessLocked(app, false, null);
-                updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+                updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
             }
         } catch (PackageManager.NameNotFoundException e) {
             throw new RuntimeException(
@@ -2502,7 +2510,7 @@
         // bind background threads to little cores
         // this is expected to fail inside of framework tests because apps can't touch cpusets directly
         // make sure we've already adjusted system_server's internal view of itself first
-        updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
         try {
             Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
                     Process.THREAD_GROUP_SYSTEM);
@@ -3387,7 +3395,7 @@
             handleAppDiedLocked(app, pid, false, true, fromBinderDied);
 
             if (doOomAdj) {
-                updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+                updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
             }
             if (doLowMem) {
                 mAppProfiler.doLowMemReportIfNeededLocked(app);
@@ -4843,7 +4851,7 @@
             }
 
             if (!didSomething) {
-                updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+                updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
                 checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
             }
 
@@ -5485,7 +5493,7 @@
                 "setProcessLimit()");
         synchronized (this) {
             mConstants.setOverrideMaxCachedProcesses(max);
-            trimApplicationsLocked(true, OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+            trimApplicationsLocked(true, OOM_ADJ_REASON_PROCESS_END);
         }
     }
 
@@ -5513,7 +5521,7 @@
                 pr.mState.setForcingToImportant(null);
                 clearProcessForegroundLocked(pr);
             }
-            updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+            updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
         }
     }
 
@@ -5560,7 +5568,7 @@
             }
 
             if (changed) {
-                updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
             }
         }
     }
@@ -6869,7 +6877,7 @@
                     new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION,
                             customProcess != null ? customProcess : info.processName));
             updateLruProcessLocked(app, false, null);
-            updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+            updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
         }
 
         // Report usage as process is persistent and being started.
@@ -6986,7 +6994,7 @@
                 mOomAdjProfiler.onWakefulnessChanged(wakefulness);
                 mOomAdjuster.onWakefulnessChanged(wakefulness);
 
-                updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY);
             }
         }
     }
@@ -7748,7 +7756,7 @@
                     }
                 }
                 if (changed) {
-                    updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                    updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
                 }
             }
         } finally {
@@ -8728,7 +8736,9 @@
         // 'recoverable' is that the app doesn't crash). Normally, for nonrecoreable native crashes,
         // debuggerd will terminate the process, but there's a backup where ActivityManager will
         // also kill it. Avoid that.
-        if (!recoverable) {
+        if (recoverable) {
+            mAppErrors.sendRecoverableCrashToAppExitInfo(r, crashInfo);
+        } else {
             mAppErrors.crashApplication(r, crashInfo);
         }
     }
@@ -9508,7 +9518,7 @@
 
             mAppProfiler.setMemFactorOverrideLocked(level);
             // Kick off an oom adj update since we forced a mem factor update.
-            updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdjLocked(OOM_ADJ_REASON_SHELL);
         }
     }
 
@@ -13408,7 +13418,7 @@
             proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
 
             // Try not to kill the process during backup
-            updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
 
             // If the process is already attached, schedule the creation of the backup agent now.
             // If it is not yet live, this will be done when it attaches to the framework.
@@ -13532,7 +13542,7 @@
 
                 // Not backing this app up any more; reset its OOM adjustment
                 final ProcessRecord proc = backupTarget.app;
-                updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+                updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
                 proc.setInFullBackup(false);
                 proc.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
 
@@ -13923,7 +13933,7 @@
                 // If we actually concluded any broadcasts, we might now be able
                 // to trim the recipients' apps from our working set
                 if (doTrim) {
-                    trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
+                    trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
                     return;
                 }
             }
@@ -15185,7 +15195,7 @@
                 queue.finishReceiverLocked(callerApp, resultCode,
                         resultData, resultExtras, resultAbort, true);
                 // updateOomAdjLocked() will be done here
-                trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
+                trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
             }
 
         } finally {
@@ -16130,7 +16140,7 @@
             item.foregroundServiceTypes = fgServiceTypes;
         }
         if (oomAdj) {
-            updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+            updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY);
         }
     }
 
@@ -16196,7 +16206,7 @@
      * {@link #enqueueOomAdjTargetLocked}.
      */
     @GuardedBy("this")
-    void updateOomAdjPendingTargetsLocked(@OomAdjuster.OomAdjReason int oomAdjReason) {
+    void updateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
         mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason);
     }
 
@@ -16215,7 +16225,7 @@
     }
 
     @GuardedBy("this")
-    final void updateOomAdjLocked(@OomAdjuster.OomAdjReason int oomAdjReason) {
+    final void updateOomAdjLocked(@OomAdjReason int oomAdjReason) {
         mOomAdjuster.updateOomAdjLocked(oomAdjReason);
     }
 
@@ -16227,8 +16237,7 @@
      * @return whether updateOomAdjLocked(app) was successful.
      */
     @GuardedBy("this")
-    final boolean updateOomAdjLocked(
-            ProcessRecord app, @OomAdjuster.OomAdjReason int oomAdjReason) {
+    final boolean updateOomAdjLocked(ProcessRecord app, @OomAdjReason int oomAdjReason) {
         return mOomAdjuster.updateOomAdjLocked(app, oomAdjReason);
     }
 
@@ -16461,16 +16470,14 @@
         mOomAdjuster.setUidTempAllowlistStateLSP(uid, onAllowlist);
     }
 
-    private void trimApplications(
-            boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) {
+    private void trimApplications(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
         synchronized (this) {
             trimApplicationsLocked(forceFullOomAdj, oomAdjReason);
         }
     }
 
     @GuardedBy("this")
-    private void trimApplicationsLocked(
-            boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) {
+    private void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
         // First remove any unused application processes whose package
         // has been removed.
         boolean didSomething = false;
@@ -17442,7 +17449,7 @@
                 }
                 pr.mState.setHasOverlayUi(hasOverlayUi);
                 //Slog.i(TAG, "Setting hasOverlayUi=" + pr.hasOverlayUi + " for pid=" + pid);
-                updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
             }
         }
 
@@ -17577,7 +17584,7 @@
 
         @Override
         public void trimApplications() {
-            ActivityManagerService.this.trimApplications(true, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+            ActivityManagerService.this.trimApplications(true, OOM_ADJ_REASON_ACTIVITY);
         }
 
         public void killProcessesForRemovedTask(ArrayList<Object> procsToKill) {
@@ -17626,9 +17633,9 @@
         }
 
         @Override
-        public void updateOomAdj() {
+        public void updateOomAdj(@OomAdjReason int oomAdjReason) {
             synchronized (ActivityManagerService.this) {
-                ActivityManagerService.this.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+                ActivityManagerService.this.updateOomAdjLocked(oomAdjReason);
             }
         }
 
@@ -18288,8 +18295,7 @@
             // sends to the activity. After this race issue between WM/ATMS and AMS is solved, this
             // workaround can be removed. (b/213288355)
             if (isNewPending) {
-                mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid,
-                        OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+                mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid, OOM_ADJ_REASON_ACTIVITY);
             }
             // We need to update the network rules for the app coming to the top state so that
             // it can access network when the device or the app is in a restricted state
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index c343ec2..061bcd7 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -555,6 +555,15 @@
         }
     }
 
+    void sendRecoverableCrashToAppExitInfo(
+            ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
+        if (r == null || crashInfo == null
+                || !"Native crash".equals(crashInfo.exceptionClassName)) return;
+        synchronized (mService) {
+            mService.mProcessList.noteAppRecoverableCrash(r);
+        }
+    }
+
     /**
      * Bring up the "unexpected error" dialog box for a crashing app.
      * Deal with edge cases (intercepts from instrumented applications,
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 4443636..4c0dd11 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -308,6 +308,16 @@
         mKillHandler.obtainMessage(KillHandler.MSG_APP_KILL, raw).sendToTarget();
     }
 
+    void scheduleNoteAppRecoverableCrash(final ProcessRecord app) {
+        if (!mAppExitInfoLoaded.get() || app == null || app.info == null) return;
+
+        ApplicationExitInfo raw = obtainRawRecord(app, System.currentTimeMillis());
+        raw.setReason(ApplicationExitInfo.REASON_CRASH_NATIVE);
+        raw.setSubReason(ApplicationExitInfo.SUBREASON_UNKNOWN);
+        raw.setDescription("recoverable_crash");
+        mKillHandler.obtainMessage(KillHandler.MSG_APP_RECOVERABLE_CRASH, raw).sendToTarget();
+    }
+
     void scheduleNoteAppKill(final int pid, final int uid, final @Reason int reason,
             final @SubReason int subReason, final String msg) {
         if (!mAppExitInfoLoaded.get()) {
@@ -421,8 +431,24 @@
         scheduleLogToStatsdLocked(info, true);
     }
 
+    /**
+     * Make note when ActivityManagerService gets a recoverable native crash, as the process isn't
+     * being killed but the crash should still be added to AppExitInfo. Also, because we're not
+     * crashing, don't log out to statsd.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void handleNoteAppRecoverableCrashLocked(final ApplicationExitInfo raw) {
+        addExitInfoLocked(raw, /* recoverable */ true);
+    }
+
     @GuardedBy("mLock")
     private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw) {
+        return addExitInfoLocked(raw, /* recoverable */ false);
+    }
+
+    @GuardedBy("mLock")
+    private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw, boolean recoverable) {
         if (!mAppExitInfoLoaded.get()) {
             Slog.w(TAG, "Skipping saving the exit info due to ongoing loading from storage");
             return null;
@@ -438,7 +464,7 @@
             }
         }
         for (int i = 0; i < packages.length; i++) {
-            addExitInfoInnerLocked(packages[i], uid, info);
+            addExitInfoInnerLocked(packages[i], uid, info, recoverable);
         }
 
         schedulePersistProcessExitInfo(false);
@@ -845,7 +871,8 @@
     }
 
     @GuardedBy("mLock")
-    private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info) {
+    private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info,
+            boolean recoverable) {
         AppExitInfoContainer container = mData.get(packageName, uid);
         if (container == null) {
             container = new AppExitInfoContainer(mAppExitInfoHistoryListSize);
@@ -859,7 +886,11 @@
             }
             mData.put(packageName, uid, container);
         }
-        container.addExitInfoLocked(info);
+        if (recoverable) {
+            container.addRecoverableCrashLocked(info);
+        } else {
+            container.addExitInfoLocked(info);
+        }
     }
 
     @GuardedBy("mLock")
@@ -1284,38 +1315,40 @@
      * A container class of {@link android.app.ApplicationExitInfo}
      */
     final class AppExitInfoContainer {
-        private SparseArray<ApplicationExitInfo> mInfos; // index is pid
+        private SparseArray<ApplicationExitInfo> mInfos; // index is a pid
+        private SparseArray<ApplicationExitInfo> mRecoverableCrashes; // index is a pid
         private int mMaxCapacity;
         private int mUid; // Application uid, not isolated uid.
 
         AppExitInfoContainer(final int maxCapacity) {
             mInfos = new SparseArray<ApplicationExitInfo>();
+            mRecoverableCrashes = new SparseArray<ApplicationExitInfo>();
             mMaxCapacity = maxCapacity;
         }
 
         @GuardedBy("mLock")
-        void getExitInfoLocked(final int filterPid, final int maxNum,
-                ArrayList<ApplicationExitInfo> results) {
+        void getInfosLocked(SparseArray<ApplicationExitInfo> map, final int filterPid,
+                final int maxNum, ArrayList<ApplicationExitInfo> results) {
             if (filterPid > 0) {
-                ApplicationExitInfo r = mInfos.get(filterPid);
+                ApplicationExitInfo r = map.get(filterPid);
                 if (r != null) {
                     results.add(r);
                 }
             } else {
-                final int numRep = mInfos.size();
+                final int numRep = map.size();
                 if (maxNum <= 0 || numRep <= maxNum) {
                     // Return all records.
                     for (int i = 0; i < numRep; i++) {
-                        results.add(mInfos.valueAt(i));
+                        results.add(map.valueAt(i));
                     }
                     Collections.sort(results,
                             (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
                 } else {
                     if (maxNum == 1) {
                         // Most of the caller might be only interested with the most recent one
-                        ApplicationExitInfo r = mInfos.valueAt(0);
+                        ApplicationExitInfo r = map.valueAt(0);
                         for (int i = 1; i < numRep; i++) {
-                            ApplicationExitInfo t = mInfos.valueAt(i);
+                            ApplicationExitInfo t = map.valueAt(i);
                             if (r.getTimestamp() < t.getTimestamp()) {
                                 r = t;
                             }
@@ -1326,7 +1359,7 @@
                         ArrayList<ApplicationExitInfo> list = mTmpInfoList2;
                         list.clear();
                         for (int i = 0; i < numRep; i++) {
-                            list.add(mInfos.valueAt(i));
+                            list.add(map.valueAt(i));
                         }
                         Collections.sort(list,
                                 (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
@@ -1340,24 +1373,30 @@
         }
 
         @GuardedBy("mLock")
-        void addExitInfoLocked(ApplicationExitInfo info) {
+        void getExitInfoLocked(final int filterPid, final int maxNum,
+                ArrayList<ApplicationExitInfo> results) {
+            getInfosLocked(mInfos, filterPid, maxNum, results);
+        }
+
+        @GuardedBy("mLock")
+        void addInfoLocked(SparseArray<ApplicationExitInfo> map, ApplicationExitInfo info) {
             int size;
-            if ((size = mInfos.size()) >= mMaxCapacity) {
+            if ((size = map.size()) >= mMaxCapacity) {
                 int oldestIndex = -1;
                 long oldestTimeStamp = Long.MAX_VALUE;
                 for (int i = 0; i < size; i++) {
-                    ApplicationExitInfo r = mInfos.valueAt(i);
+                    ApplicationExitInfo r = map.valueAt(i);
                     if (r.getTimestamp() < oldestTimeStamp) {
                         oldestTimeStamp = r.getTimestamp();
                         oldestIndex = i;
                     }
                 }
                 if (oldestIndex >= 0) {
-                    final File traceFile = mInfos.valueAt(oldestIndex).getTraceFile();
+                    final File traceFile = map.valueAt(oldestIndex).getTraceFile();
                     if (traceFile != null) {
                         traceFile.delete();
                     }
-                    mInfos.removeAt(oldestIndex);
+                    map.removeAt(oldestIndex);
                 }
             }
             // Claim the state information if there is any
@@ -1367,7 +1406,17 @@
                     mActiveAppStateSummary, uid, pid));
             info.setTraceFile(findAndRemoveFromSparse2dArray(mActiveAppTraces, uid, pid));
             info.setAppTraceRetriever(mAppTraceRetriever);
-            mInfos.append(pid, info);
+            map.append(pid, info);
+        }
+
+        @GuardedBy("mLock")
+        void addExitInfoLocked(ApplicationExitInfo info) {
+            addInfoLocked(mInfos, info);
+        }
+
+        @GuardedBy("mLock")
+        void addRecoverableCrashLocked(ApplicationExitInfo info) {
+            addInfoLocked(mRecoverableCrashes, info);
         }
 
         @GuardedBy("mLock")
@@ -1382,9 +1431,9 @@
         }
 
         @GuardedBy("mLock")
-        void destroyLocked() {
-            for (int i = mInfos.size() - 1; i >= 0; i--) {
-                ApplicationExitInfo ai = mInfos.valueAt(i);
+        void destroyLocked(SparseArray<ApplicationExitInfo> map) {
+            for (int i = map.size() - 1; i >= 0; i--) {
+                ApplicationExitInfo ai = map.valueAt(i);
                 final File traceFile = ai.getTraceFile();
                 if (traceFile != null) {
                     traceFile.delete();
@@ -1395,24 +1444,37 @@
         }
 
         @GuardedBy("mLock")
+        void destroyLocked() {
+            destroyLocked(mInfos);
+            destroyLocked(mRecoverableCrashes);
+        }
+
+        @GuardedBy("mLock")
         void forEachRecordLocked(final BiFunction<Integer, ApplicationExitInfo, Integer> callback) {
-            if (callback != null) {
-                for (int i = mInfos.size() - 1; i >= 0; i--) {
-                    switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) {
-                        case FOREACH_ACTION_REMOVE_ITEM:
-                            final File traceFile = mInfos.valueAt(i).getTraceFile();
-                            if (traceFile != null) {
-                                traceFile.delete();
-                            }
-                            mInfos.removeAt(i);
-                            break;
-                        case FOREACH_ACTION_STOP_ITERATION:
-                            i = 0;
-                            break;
-                        case FOREACH_ACTION_NONE:
-                        default:
-                            break;
-                    }
+            if (callback == null) return;
+            for (int i = mInfos.size() - 1; i >= 0; i--) {
+                switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) {
+                    case FOREACH_ACTION_STOP_ITERATION: return;
+                    case FOREACH_ACTION_REMOVE_ITEM:
+                        final File traceFile = mInfos.valueAt(i).getTraceFile();
+                        if (traceFile != null) {
+                            traceFile.delete();
+                        }
+                        mInfos.removeAt(i);
+                        break;
+                }
+            }
+            for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+                switch (callback.apply(
+                        mRecoverableCrashes.keyAt(i), mRecoverableCrashes.valueAt(i))) {
+                    case FOREACH_ACTION_STOP_ITERATION: return;
+                    case FOREACH_ACTION_REMOVE_ITEM:
+                        final File traceFile = mRecoverableCrashes.valueAt(i).getTraceFile();
+                        if (traceFile != null) {
+                            traceFile.delete();
+                        }
+                        mRecoverableCrashes.removeAt(i);
+                        break;
                 }
             }
         }
@@ -1423,6 +1485,9 @@
             for (int i = mInfos.size() - 1; i >= 0; i--) {
                 list.add(mInfos.valueAt(i));
             }
+            for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+                list.add(mRecoverableCrashes.valueAt(i));
+            }
             Collections.sort(list, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
             int size = list.size();
             for (int i = 0; i < size; i++) {
@@ -1434,10 +1499,13 @@
         void writeToProto(ProtoOutputStream proto, long fieldId) {
             long token = proto.start(fieldId);
             proto.write(AppsExitInfoProto.Package.User.UID, mUid);
-            int size = mInfos.size();
-            for (int i = 0; i < size; i++) {
+            for (int i = 0; i < mInfos.size(); i++) {
                 mInfos.valueAt(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
             }
+            for (int i = 0; i < mRecoverableCrashes.size(); i++) {
+                mRecoverableCrashes.valueAt(i).writeToProto(
+                        proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH);
+            }
             proto.end(token);
         }
 
@@ -1448,14 +1516,23 @@
                     next != ProtoInputStream.NO_MORE_FIELDS;
                     next = proto.nextField()) {
                 switch (next) {
-                    case (int) AppsExitInfoProto.Package.User.UID:
+                    case (int) AppsExitInfoProto.Package.User.UID: {
                         mUid = proto.readInt(AppsExitInfoProto.Package.User.UID);
                         break;
-                    case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO:
+                    }
+                    case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO: {
                         ApplicationExitInfo info = new ApplicationExitInfo();
                         info.readFromProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
                         mInfos.put(info.getPid(), info);
                         break;
+                    }
+                    case (int) AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH: {
+                        ApplicationExitInfo info = new ApplicationExitInfo();
+                        info.readFromProto(
+                                proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH);
+                        mRecoverableCrashes.put(info.getPid(), info);
+                        break;
+                    }
                 }
             }
             proto.end(token);
@@ -1472,6 +1549,11 @@
                     list.add(mInfos.valueAt(i));
                 }
             }
+            for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+                if (filterPid == 0 || filterPid == mRecoverableCrashes.keyAt(i)) {
+                    list.add(mRecoverableCrashes.valueAt(i));
+                }
+            }
             return list;
         }
     }
@@ -1610,6 +1692,7 @@
         static final int MSG_PROC_DIED = 4103;
         static final int MSG_APP_KILL = 4104;
         static final int MSG_STATSD_LOG = 4105;
+        static final int MSG_APP_RECOVERABLE_CRASH = 4106;
 
         KillHandler(Looper looper) {
             super(looper, null, true);
@@ -1648,6 +1731,14 @@
                     }
                 }
                 break;
+                case MSG_APP_RECOVERABLE_CRASH: {
+                    ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
+                    synchronized (mLock) {
+                        handleNoteAppRecoverableCrashLocked(raw);
+                    }
+                    recycleRawRecord(raw);
+                }
+                break;
                 default:
                     super.handleMessage(msg);
             }
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index bfc8251..0767218 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -18,9 +18,9 @@
 
 import static com.android.internal.util.Preconditions.checkState;
 import static com.android.server.am.BroadcastRecord.deliveryStateToString;
-import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
 import static com.android.server.am.BroadcastRecord.isReceiverEquals;
 
+import android.annotation.CheckResult;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -188,6 +188,12 @@
     private @Reason int mRunnableAtReason = REASON_EMPTY;
     private boolean mRunnableAtInvalidated;
 
+    /**
+     * Last state applied by {@link #updateDeferredStates}, used to quickly
+     * determine if a state transition is occurring.
+     */
+    private boolean mLastDeferredStates;
+
     private boolean mUidCached;
     private boolean mProcessInstrumented;
     private boolean mProcessPersistent;
@@ -236,7 +242,15 @@
      */
     @Nullable
     public BroadcastRecord enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record,
-            int recordIndex, boolean wouldBeSkipped) {
+            int recordIndex, boolean wouldBeSkipped,
+            @NonNull BroadcastConsumer deferredStatesApplyConsumer) {
+        // When updateDeferredStates() has already applied a deferred state to
+        // all pending items, apply to this new broadcast too
+        if (mLastDeferredStates && record.deferUntilActive
+                && (record.getDeliveryState(recordIndex) == BroadcastRecord.DELIVERY_PENDING)) {
+            deferredStatesApplyConsumer.accept(record, recordIndex);
+        }
+
         if (record.isReplacePending()) {
             final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex,
                     wouldBeSkipped);
@@ -341,7 +355,12 @@
      * Predicates that choose to remove a broadcast <em>must</em> finish
      * delivery of the matched broadcast, to ensure that situations like ordered
      * broadcasts are handled consistently.
+     *
+     * @return if this operation may have changed internal state, indicating
+     *         that the caller is responsible for invoking
+     *         {@link BroadcastQueueModernImpl#updateRunnableList}
      */
+    @CheckResult
     public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate,
             @NonNull BroadcastConsumer consumer, boolean andRemove) {
         boolean didSomething = false;
@@ -354,6 +373,7 @@
         return didSomething;
     }
 
+    @CheckResult
     private boolean forEachMatchingBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
             @NonNull BroadcastPredicate predicate, @NonNull BroadcastConsumer consumer,
             boolean andRemove) {
@@ -370,6 +390,10 @@
                     args.recycle();
                     it.remove();
                     onBroadcastDequeued(record, recordIndex, recordWouldBeSkipped);
+                } else {
+                    // Even if we're leaving broadcast in queue, it may have
+                    // been mutated in such a way to change our runnable time
+                    invalidateRunnableAt();
                 }
                 didSomething = true;
             }
@@ -381,32 +405,44 @@
 
     /**
      * Update the actively running "warm" process for this process.
+     *
+     * @return if this operation may have changed internal state, indicating
+     *         that the caller is responsible for invoking
+     *         {@link BroadcastQueueModernImpl#updateRunnableList}
      */
-    public void setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) {
+    @CheckResult
+    public boolean setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) {
         this.app = app;
-        if (app != null) {
-            setUidCached(uidCached);
-            setProcessInstrumented(app.getActiveInstrumentation() != null);
-            setProcessPersistent(app.isPersistent());
-        } else {
-            setUidCached(uidCached);
-            setProcessInstrumented(false);
-            setProcessPersistent(false);
-        }
 
         // Since we may have just changed our PID, invalidate cached strings
         mCachedToString = null;
         mCachedToShortString = null;
+
+        boolean didSomething = false;
+        if (app != null) {
+            didSomething |= setUidCached(uidCached);
+            didSomething |= setProcessInstrumented(app.getActiveInstrumentation() != null);
+            didSomething |= setProcessPersistent(app.isPersistent());
+        } else {
+            didSomething |= setUidCached(uidCached);
+            didSomething |= setProcessInstrumented(false);
+            didSomething |= setProcessPersistent(false);
+        }
+        return didSomething;
     }
 
     /**
      * Update if this process is in the "cached" state, typically signaling that
      * broadcast dispatch should be paused or delayed.
      */
-    private void setUidCached(boolean uidCached) {
+    @CheckResult
+    private boolean setUidCached(boolean uidCached) {
         if (mUidCached != uidCached) {
             mUidCached = uidCached;
             invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
         }
     }
 
@@ -415,10 +451,14 @@
      * signaling that broadcast dispatch should bypass all pauses or delays, to
      * avoid holding up test suites.
      */
-    private void setProcessInstrumented(boolean instrumented) {
+    @CheckResult
+    private boolean setProcessInstrumented(boolean instrumented) {
         if (mProcessInstrumented != instrumented) {
             mProcessInstrumented = instrumented;
             invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
         }
     }
 
@@ -426,10 +466,14 @@
      * Update if this process is in the "persistent" state, which signals broadcast dispatch should
      * bypass all pauses or delays to prevent the system from becoming out of sync with itself.
      */
-    private void setProcessPersistent(boolean persistent) {
+    @CheckResult
+    private boolean setProcessPersistent(boolean persistent) {
         if (mProcessPersistent != persistent) {
             mProcessPersistent = persistent;
             invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
         }
     }
 
@@ -649,8 +693,20 @@
         return mActive != null;
     }
 
-    void forceDelayBroadcastDelivery(long delayedDurationMs) {
-        mForcedDelayedDurationMs = delayedDurationMs;
+    /**
+     * @return if this operation may have changed internal state, indicating
+     *         that the caller is responsible for invoking
+     *         {@link BroadcastQueueModernImpl#updateRunnableList}
+     */
+    @CheckResult
+    boolean forceDelayBroadcastDelivery(long delayedDurationMs) {
+        if (mForcedDelayedDurationMs != delayedDurationMs) {
+            mForcedDelayedDurationMs = delayedDurationMs;
+            invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
+        }
     }
 
     /**
@@ -709,7 +765,7 @@
                 || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit);
         final boolean isLPQueueEligible = shouldConsiderLPQueue
                 && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
-                && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex);
+                && !nextLPRecord.isBlocked(nextLPRecordIndex);
         return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue;
     }
 
@@ -722,10 +778,21 @@
      * broadcasts would be prioritized for dispatching, even if there are urgent broadcasts
      * waiting. This is typically used in case there are callers waiting for "barrier" to be
      * reached.
+     *
+     * @return if this operation may have changed internal state, indicating
+     *         that the caller is responsible for invoking
+     *         {@link BroadcastQueueModernImpl#updateRunnableList}
      */
+    @CheckResult
     @VisibleForTesting
-    void setPrioritizeEarliest(boolean prioritizeEarliest) {
-        mPrioritizeEarliest = prioritizeEarliest;
+    boolean setPrioritizeEarliest(boolean prioritizeEarliest) {
+        if (mPrioritizeEarliest != prioritizeEarliest) {
+            mPrioritizeEarliest = prioritizeEarliest;
+            invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
+        }
     }
 
     /**
@@ -912,39 +979,20 @@
         }
     }
 
-    private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) {
-        final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
-
-        int existingDeferredCount = 0;
-        if (r.deferUntilActive) {
-            for (int i = 0; i < index; i++) {
-                if (r.deferredUntilActive[i]) existingDeferredCount++;
-            }
-        }
-
-        // We might be blocked waiting for other receivers to finish,
-        // typically for an ordered broadcast or priority traunches
-        if ((r.terminalCount + existingDeferredCount) < blockedUntilTerminalCount
-                && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
-            return true;
-        }
-        return false;
-    }
-
     /**
-     * Update {@link #getRunnableAt()} if it's currently invalidated.
+     * Update {@link #getRunnableAt()}, when needed.
      */
-    private void updateRunnableAt() {
-        final SomeArgs next = peekNextBroadcast();
+    void updateRunnableAt() {
+        if (!mRunnableAtInvalidated) return;
         mRunnableAtInvalidated = false;
+
+        final SomeArgs next = peekNextBroadcast();
         if (next != null) {
             final BroadcastRecord r = (BroadcastRecord) next.arg1;
             final int index = next.argi1;
             final long runnableAt = r.enqueueTime;
 
-            // If we're specifically queued behind other ordered dispatch activity,
-            // we aren't runnable yet
-            if (blockedOnOrderedDispatch(r, index)) {
+            if (r.isBlocked(index)) {
                 mRunnableAt = Long.MAX_VALUE;
                 mRunnableAtReason = REASON_BLOCKED;
                 return;
@@ -1047,10 +1095,44 @@
     }
 
     /**
+     * Update {@link BroadcastRecord.DELIVERY_DEFERRED} states of all our
+     * pending broadcasts, when needed.
+     */
+    void updateDeferredStates(@NonNull BroadcastConsumer applyConsumer,
+            @NonNull BroadcastConsumer clearConsumer) {
+        // When all we have pending is deferred broadcasts, and we're cached,
+        // then we want everything to be marked deferred
+        final boolean wantDeferredStates = (mCountDeferred > 0)
+                && (mCountDeferred == mCountEnqueued) && mUidCached;
+
+        if (mLastDeferredStates != wantDeferredStates) {
+            mLastDeferredStates = wantDeferredStates;
+            if (wantDeferredStates) {
+                forEachMatchingBroadcast((r, i) -> {
+                    return r.deferUntilActive
+                            && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_PENDING);
+                }, applyConsumer, false);
+            } else {
+                forEachMatchingBroadcast((r, i) -> {
+                    return r.deferUntilActive
+                            && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_DEFERRED);
+                }, clearConsumer, false);
+            }
+        }
+    }
+
+    /**
      * Check overall health, confirming things are in a reasonable state and
      * that we're not wedged.
      */
     public void assertHealthLocked() {
+        // If we're not actively running, we should be sorted into the runnable
+        // list, and if we're invalidated then someone likely forgot to invoke
+        // updateRunnableList() to re-sort us into place
+        if (!isActive()) {
+            checkState(!mRunnableAtInvalidated, "mRunnableAtInvalidated");
+        }
+
         assertHealthLocked(mPending);
         assertHealthLocked(mPendingUrgent);
         assertHealthLocked(mPendingOffload);
@@ -1153,19 +1235,30 @@
         return mCachedToShortString;
     }
 
+    public String describeStateLocked() {
+        return describeStateLocked(SystemClock.uptimeMillis());
+    }
+
+    public String describeStateLocked(@UptimeMillisLong long now) {
+        final StringBuilder sb = new StringBuilder();
+        if (isRunnable()) {
+            sb.append("runnable at ");
+            TimeUtils.formatDuration(getRunnableAt(), now, sb);
+        } else {
+            sb.append("not runnable");
+        }
+        sb.append(" because ");
+        sb.append(reasonToString(mRunnableAtReason));
+        return sb.toString();
+    }
+
     @NeverCompile
     public void dumpLocked(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw) {
         if ((mActive == null) && isEmpty()) return;
 
         pw.print(toShortString());
-        if (isRunnable()) {
-            pw.print(" runnable at ");
-            TimeUtils.formatDuration(getRunnableAt(), now, pw);
-        } else {
-            pw.print(" not runnable");
-        }
-        pw.print(" because ");
-        pw.print(reasonToString(mRunnableAtReason));
+        pw.print(" ");
+        pw.print(describeStateLocked(now));
         pw.println();
 
         pw.increaseIndent();
@@ -1262,12 +1355,12 @@
             pw.print(info.activityInfo.name);
         }
         pw.println();
-        final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex];
-        if (blockedUntilTerminalCount != -1) {
+        final int blockedUntilBeyondCount = record.blockedUntilBeyondCount[recordIndex];
+        if (blockedUntilBeyondCount != -1) {
             pw.print("    blocked until ");
-            pw.print(blockedUntilTerminalCount);
+            pw.print(blockedUntilBeyondCount);
             pw.print(", currently at ");
-            pw.print(record.terminalCount);
+            pw.print(record.beyondCount);
             pw.print(" of ");
             pw.println(record.receivers.size());
         }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index bd36c3f..5a4d315 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
 import static android.text.TextUtils.formatSimple;
@@ -37,7 +38,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a4bdf61..8735f8a 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
 
@@ -38,7 +39,6 @@
 import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
 import static com.android.server.am.BroadcastRecord.getReceiverUid;
 import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -327,6 +327,12 @@
             return;
         }
 
+        // To place ourselves correctly in the runnable list, we may need to
+        // update internals that may have been invalidated; we wait until now at
+        // the last possible moment to avoid duplicated work
+        queue.updateDeferredStates(mBroadcastConsumerDeferApply, mBroadcastConsumerDeferClear);
+        queue.updateRunnableAt();
+
         final boolean wantQueue = queue.isRunnable();
         final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null)
                 || (queue.runnableAtNext != null);
@@ -352,8 +358,6 @@
         // If app isn't running, and there's nothing in the queue, clean up
         if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
             removeProcessQueue(queue.processName, queue.uid);
-        } else {
-            updateQueueDeferred(queue);
         }
     }
 
@@ -619,14 +623,10 @@
             }
             enqueuedBroadcast = true;
             final BroadcastRecord replacedBroadcast = queue.enqueueOrReplaceBroadcast(
-                    r, i, wouldBeSkipped);
+                    r, i, wouldBeSkipped, mBroadcastConsumerDeferApply);
             if (replacedBroadcast != null) {
                 replacedBroadcasts.add(replacedBroadcast);
             }
-            if (r.isDeferUntilActive() && queue.isDeferredUntilActive()) {
-                setDeliveryState(queue, null, r, i, receiver, BroadcastRecord.DELIVERY_DEFERRED,
-                        "deferred at enqueue time");
-            }
             updateRunnableList(queue);
             enqueueUpdateRunningList();
         }
@@ -1008,6 +1008,7 @@
         }
 
         final BroadcastRecord r = queue.getActive();
+        final int index = queue.getActiveIndex();
         if (r.ordered) {
             r.resultCode = resultCode;
             r.resultData = resultData;
@@ -1015,18 +1016,24 @@
             if (!r.isNoAbort()) {
                 r.resultAbort = resultAbort;
             }
+        }
 
-            // When the caller aborted an ordered broadcast, we mark all
-            // remaining receivers as skipped
-            if (r.resultAbort) {
-                for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) {
-                    setDeliveryState(null, null, r, i, r.receivers.get(i),
-                            BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
-                }
+        // To ensure that "beyond" high-water marks are updated in a monotonic
+        // way, we finish this receiver before possibly skipping any remaining
+        // aborted receivers
+        final boolean res = finishReceiverActiveLocked(queue,
+                BroadcastRecord.DELIVERY_DELIVERED, "remote app");
+
+        // When the caller aborted an ordered broadcast, we mark all
+        // remaining receivers as skipped
+        if (r.resultAbort) {
+            for (int i = index + 1; i < r.receivers.size(); i++) {
+                setDeliveryState(null, null, r, i, r.receivers.get(i),
+                        BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
             }
         }
 
-        return finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
+        return res;
     }
 
     /**
@@ -1108,21 +1115,10 @@
             @NonNull Object receiver, @DeliveryState int newDeliveryState,
             @NonNull String reason) {
         final int cookie = traceBegin("setDeliveryState");
-        final int oldDeliveryState = getDeliveryState(r, index);
-        boolean checkFinished = false;
 
-        // Only apply state when we haven't already reached a terminal state;
-        // this is how we ignore racing timeout messages
-        if (!isDeliveryStateTerminal(oldDeliveryState)) {
-            r.setDeliveryState(index, newDeliveryState, reason);
-            if (oldDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
-                r.deferredCount--;
-            } else if (newDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
-                // If we're deferring a broadcast, maybe that's enough to unblock the final callback
-                r.deferredCount++;
-                checkFinished = true;
-            }
-        }
+        // Remember the old state and apply the new state
+        final int oldDeliveryState = getDeliveryState(r, index);
+        final boolean beyondCountChanged = r.setDeliveryState(index, newDeliveryState, reason);
 
         // Emit any relevant tracing results when we're changing the delivery
         // state as part of running from a queue
@@ -1147,15 +1143,13 @@
                         + deliveryStateToString(newDeliveryState) + " because " + reason);
             }
 
-            r.terminalCount++;
             notifyFinishReceiver(queue, app, r, index, receiver);
-            checkFinished = true;
         }
-        // When entire ordered broadcast finished, deliver final result
-        if (checkFinished) {
-            final boolean recordFinished =
-                    ((r.terminalCount + r.deferredCount) == r.receivers.size());
-            if (recordFinished) {
+
+        // When we've reached a new high-water mark, we might be in a position
+        // to unblock other receivers or the final resultTo
+        if (beyondCountChanged) {
+            if (r.beyondCount == r.receivers.size()) {
                 scheduleResultTo(r);
             }
 
@@ -1255,14 +1249,14 @@
         r.resultExtras = null;
     };
 
-    private final BroadcastConsumer mBroadcastConsumerDefer = (r, i) -> {
+    private final BroadcastConsumer mBroadcastConsumerDeferApply = (r, i) -> {
         setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_DEFERRED,
-                "mBroadcastConsumerDefer");
+                "mBroadcastConsumerDeferApply");
     };
 
-    private final BroadcastConsumer mBroadcastConsumerUndoDefer = (r, i) -> {
+    private final BroadcastConsumer mBroadcastConsumerDeferClear = (r, i) -> {
         setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_PENDING,
-                "mBroadcastConsumerUndoDefer");
+                "mBroadcastConsumerDeferClear");
     };
 
     /**
@@ -1278,7 +1272,8 @@
                     final long now = SystemClock.uptimeMillis();
                     if (now > mLastTestFailureTime + DateUtils.SECOND_IN_MILLIS) {
                         mLastTestFailureTime = now;
-                        pw.println("Test " + label + " failed due to " + leaf.toShortString());
+                        pw.println("Test " + label + " failed due to " + leaf.toShortString() + " "
+                                + leaf.describeStateLocked());
                         pw.flush();
                     }
                     return false;
@@ -1315,34 +1310,25 @@
         return didSomething;
     }
 
-    private void forEachMatchingQueue(
+    private boolean forEachMatchingQueue(
             @NonNull Predicate<BroadcastProcessQueue> queuePredicate,
             @NonNull Consumer<BroadcastProcessQueue> queueConsumer) {
+        boolean didSomething = false;
         for (int i = mProcessQueues.size() - 1; i >= 0; i--) {
             BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
             while (leaf != null) {
                 if (queuePredicate.test(leaf)) {
                     queueConsumer.accept(leaf);
                     updateRunnableList(leaf);
+                    didSomething = true;
                 }
                 leaf = leaf.processNameNext;
             }
         }
-    }
-
-    private void updateQueueDeferred(
-            @NonNull BroadcastProcessQueue leaf) {
-        if (leaf.isDeferredUntilActive()) {
-            leaf.forEachMatchingBroadcast((r, i) -> {
-                return r.deferUntilActive && (r.getDeliveryState(i)
-                        == BroadcastRecord.DELIVERY_PENDING);
-            }, mBroadcastConsumerDefer, false);
-        } else if (leaf.hasDeferredBroadcasts()) {
-            leaf.forEachMatchingBroadcast((r, i) -> {
-                return r.deferUntilActive && (r.getDeliveryState(i)
-                        == BroadcastRecord.DELIVERY_DEFERRED);
-            }, mBroadcastConsumerUndoDefer, false);
+        if (didSomething) {
+            enqueueUpdateRunningList();
         }
+        return didSomething;
     }
 
     @Override
@@ -1365,8 +1351,6 @@
                         // Update internal state by refreshing values previously
                         // read from any known running process
                         setQueueProcess(leaf, leaf.app);
-                        updateQueueDeferred(leaf);
-                        updateRunnableList(leaf);
                         leaf = leaf.processNameNext;
                     }
                     enqueueUpdateRunningList();
@@ -1535,19 +1519,31 @@
         }
     }
 
+    @SuppressWarnings("CheckResult")
     private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
         if (!queue.isProcessWarm()) {
-            setQueueProcess(queue, mService.getProcessRecordLocked(queue.processName, queue.uid));
+            // This is a bit awkward; we're in the middle of traversing the
+            // runnable queue, so we can't reorder that list if the runnable
+            // time changes here. However, if this process was just found to be
+            // warm via this operation, we're going to immediately promote it to
+            // be running, and any side effect of this operation will then apply
+            // after it's finished and is returned to the runnable list.
+            queue.setProcessAndUidCached(
+                    mService.getProcessRecordLocked(queue.processName, queue.uid),
+                    mUidCached.get(queue.uid, false));
         }
     }
 
     /**
      * Update the {@link ProcessRecord} associated with the given
-     * {@link BroadcastProcessQueue}.
+     * {@link BroadcastProcessQueue}. Also updates any runnable status that
+     * might have changed as a side-effect.
      */
     private void setQueueProcess(@NonNull BroadcastProcessQueue queue,
             @Nullable ProcessRecord app) {
-        queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false));
+        if (queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false))) {
+            updateRunnableList(queue);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index c368290..64fe393 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -24,6 +24,7 @@
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;
 
+import android.annotation.CheckResult;
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
@@ -101,8 +102,7 @@
     final @NonNull List<Object> receivers;   // contains BroadcastFilter and ResolveInfo
     final @DeliveryState int[] delivery;   // delivery state of each receiver
     final @NonNull String[] deliveryReasons; // reasons for delivery state of each receiver
-    final boolean[] deferredUntilActive; // whether each receiver is infinitely deferred
-    final int[] blockedUntilTerminalCount; // blocked until count of each receiver
+    final int[] blockedUntilBeyondCount; // blocked until count of each receiver
     @Nullable ProcessRecord resultToApp; // who receives final result if non-null
     @Nullable IIntentReceiver resultTo; // who receives final result if non-null
     boolean deferred;
@@ -134,6 +134,7 @@
     int manifestSkipCount;  // number of manifest receivers skipped.
     int terminalCount;      // number of receivers in terminal state.
     int deferredCount;      // number of receivers in deferred state.
+    int beyondCount;        // high-water number of receivers we've moved beyond.
     @Nullable BroadcastQueue queue;   // the outbound queue handling this broadcast
 
     // Determines the privileges the app's process has in regard to background starts.
@@ -219,6 +220,23 @@
         }
     }
 
+    /**
+     * Return if the given delivery state is "beyond", which means that we've
+     * moved beyond this receiver, and future receivers are now unblocked.
+     */
+    static boolean isDeliveryStateBeyond(@DeliveryState int deliveryState) {
+        switch (deliveryState) {
+            case DELIVERY_DELIVERED:
+            case DELIVERY_SKIPPED:
+            case DELIVERY_TIMEOUT:
+            case DELIVERY_FAILURE:
+            case DELIVERY_DEFERRED:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     ProcessRecord curApp;       // hosting application of current receiver.
     ComponentName curComponent; // the receiver class that is currently running.
     ActivityInfo curReceiver;   // the manifest receiver that is currently running.
@@ -356,7 +374,7 @@
                 TimeUtils.formatDuration(terminalTime[i] - scheduledTime[i], pw);
                 pw.print(' ');
             }
-            pw.print("("); pw.print(blockedUntilTerminalCount[i]); pw.print(") ");
+            pw.print("("); pw.print(blockedUntilBeyondCount[i]); pw.print(") ");
             pw.print("#"); pw.print(i); pw.print(": ");
             if (o instanceof BroadcastFilter) {
                 pw.println(o);
@@ -411,8 +429,7 @@
         urgent = calculateUrgent(_intent, _options);
         deferUntilActive = calculateDeferUntilActive(_callingUid,
                 _options, _resultTo, _serialized, urgent);
-        deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0];
-        blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
+        blockedUntilBeyondCount = calculateBlockedUntilBeyondCount(receivers, _serialized);
         scheduledTime = new long[delivery.length];
         terminalTime = new long[delivery.length];
         resultToApp = _resultToApp;
@@ -423,7 +440,7 @@
         ordered = _serialized;
         sticky = _sticky;
         initialSticky = _initialSticky;
-        prioritized = isPrioritized(blockedUntilTerminalCount, _serialized);
+        prioritized = isPrioritized(blockedUntilBeyondCount, _serialized);
         userId = _userId;
         nextReceiver = 0;
         state = IDLE;
@@ -467,8 +484,7 @@
         delivery = from.delivery;
         deliveryReasons = from.deliveryReasons;
         deferUntilActive = from.deferUntilActive;
-        deferredUntilActive = from.deferredUntilActive;
-        blockedUntilTerminalCount = from.blockedUntilTerminalCount;
+        blockedUntilBeyondCount = from.blockedUntilBeyondCount;
         scheduledTime = from.scheduledTime;
         terminalTime = from.terminalTime;
         resultToApp = from.resultToApp;
@@ -627,32 +643,72 @@
     /**
      * Update the delivery state of the given {@link #receivers} index.
      * Automatically updates any time measurements related to state changes.
+     *
+     * @return if {@link #beyondCount} changed due to this state transition,
+     *         indicating that other events may be unblocked.
      */
-    void setDeliveryState(int index, @DeliveryState int deliveryState,
+    @CheckResult
+    boolean setDeliveryState(int index, @DeliveryState int newDeliveryState,
             @NonNull String reason) {
-        delivery[index] = deliveryState;
-        deliveryReasons[index] = reason;
-        if (deferUntilActive) deferredUntilActive[index] = false;
-        switch (deliveryState) {
+        final int oldDeliveryState = delivery[index];
+        if (isDeliveryStateTerminal(oldDeliveryState)
+                || newDeliveryState == oldDeliveryState) {
+            // We've already arrived in terminal or requested state, so leave
+            // any statistics and reasons intact from the first transition
+            return false;
+        }
+
+        switch (oldDeliveryState) {
+            case DELIVERY_DEFERRED:
+                deferredCount--;
+                break;
+        }
+        switch (newDeliveryState) {
+            case DELIVERY_SCHEDULED:
+                scheduledTime[index] = SystemClock.uptimeMillis();
+                break;
+            case DELIVERY_DEFERRED:
+                deferredCount++;
+                break;
             case DELIVERY_DELIVERED:
             case DELIVERY_SKIPPED:
             case DELIVERY_TIMEOUT:
             case DELIVERY_FAILURE:
                 terminalTime[index] = SystemClock.uptimeMillis();
-                break;
-            case DELIVERY_SCHEDULED:
-                scheduledTime[index] = SystemClock.uptimeMillis();
-                break;
-            case DELIVERY_DEFERRED:
-                if (deferUntilActive) deferredUntilActive[index] = true;
+                terminalCount++;
                 break;
         }
+
+        delivery[index] = newDeliveryState;
+        deliveryReasons[index] = reason;
+
+        // If this state change might bring us to a new high-water mark, bring
+        // ourselves as high as we possibly can
+        final int oldBeyondCount = beyondCount;
+        if (index >= beyondCount) {
+            for (int i = beyondCount; i < delivery.length; i++) {
+                if (isDeliveryStateBeyond(getDeliveryState(i))) {
+                    beyondCount = i + 1;
+                } else {
+                    break;
+                }
+            }
+        }
+        return (beyondCount != oldBeyondCount);
     }
 
     @DeliveryState int getDeliveryState(int index) {
         return delivery[index];
     }
 
+    /**
+     * @return if the given {@link #receivers} index should be considered
+     *         blocked based on the current status of the overall broadcast.
+     */
+    boolean isBlocked(int index) {
+        return (beyondCount < blockedUntilBeyondCount[index]);
+    }
+
     boolean wasDeliveryAttempted(int index) {
         final int deliveryState = getDeliveryState(index);
         switch (deliveryState) {
@@ -757,36 +813,36 @@
      * has prioritized tranches of receivers.
      */
     @VisibleForTesting
-    static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount,
+    static boolean isPrioritized(@NonNull int[] blockedUntilBeyondCount,
             boolean ordered) {
-        return !ordered && (blockedUntilTerminalCount.length > 0)
-                && (blockedUntilTerminalCount[0] != -1);
+        return !ordered && (blockedUntilBeyondCount.length > 0)
+                && (blockedUntilBeyondCount[0] != -1);
     }
 
     /**
-     * Calculate the {@link #terminalCount} that each receiver should be
+     * Calculate the {@link #beyondCount} that each receiver should be
      * considered blocked until.
      * <p>
      * For example, in an ordered broadcast, receiver {@code N} is blocked until
-     * receiver {@code N-1} reaches a terminal state. Similarly, in a
-     * prioritized broadcast, receiver {@code N} is blocked until all receivers
-     * of a higher priority reach a terminal state.
+     * receiver {@code N-1} reaches a terminal or deferred state. Similarly, in
+     * a prioritized broadcast, receiver {@code N} is blocked until all
+     * receivers of a higher priority reach a terminal or deferred state.
      * <p>
-     * When there are no terminal count constraints, the blocked value for each
+     * When there are no beyond count constraints, the blocked value for each
      * receiver is {@code -1}.
      */
     @VisibleForTesting
-    static @NonNull int[] calculateBlockedUntilTerminalCount(
+    static @NonNull int[] calculateBlockedUntilBeyondCount(
             @NonNull List<Object> receivers, boolean ordered) {
         final int N = receivers.size();
-        final int[] blockedUntilTerminalCount = new int[N];
+        final int[] blockedUntilBeyondCount = new int[N];
         int lastPriority = 0;
         int lastPriorityIndex = 0;
         for (int i = 0; i < N; i++) {
             if (ordered) {
                 // When sending an ordered broadcast, we need to block this
                 // receiver until all previous receivers have terminated
-                blockedUntilTerminalCount[i] = i;
+                blockedUntilBeyondCount[i] = i;
             } else {
                 // When sending a prioritized broadcast, we only need to wait
                 // for the previous tranche of receivers to be terminated
@@ -794,18 +850,18 @@
                 if ((i == 0) || (thisPriority != lastPriority)) {
                     lastPriority = thisPriority;
                     lastPriorityIndex = i;
-                    blockedUntilTerminalCount[i] = i;
+                    blockedUntilBeyondCount[i] = i;
                 } else {
-                    blockedUntilTerminalCount[i] = lastPriorityIndex;
+                    blockedUntilBeyondCount[i] = lastPriorityIndex;
                 }
             }
         }
         // If the entire list is in the same priority tranche, mark as -1 to
         // indicate that none of them need to wait
-        if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) {
-            Arrays.fill(blockedUntilTerminalCount, -1);
+        if (N > 0 && blockedUntilBeyondCount[N - 1] == 0) {
+            Arrays.fill(blockedUntilBeyondCount, -1);
         }
-        return blockedUntilTerminalCount;
+        return blockedUntilBeyondCount;
     }
 
     static int getReceiverUid(@NonNull Object receiver) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 568997b..8ad76cb 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -18,6 +18,28 @@
 
 import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
 import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
 import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
@@ -26,6 +48,7 @@
 
 import android.annotation.IntDef;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityThread;
 import android.app.ApplicationExitInfo;
 import android.app.IApplicationThread;
@@ -139,6 +162,26 @@
             FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BINDER_TXNS;
     static final int UNFREEZE_REASON_FEATURE_FLAGS =
             FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FEATURE_FLAGS;
+    static final int UNFREEZE_REASON_SHORT_FGS_TIMEOUT =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHORT_FGS_TIMEOUT;
+    static final int UNFREEZE_REASON_SYSTEM_INIT =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SYSTEM_INIT;
+    static final int UNFREEZE_REASON_BACKUP =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BACKUP;
+    static final int UNFREEZE_REASON_SHELL =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHELL;
+    static final int UNFREEZE_REASON_REMOVE_TASK =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_REMOVE_TASK;
+    static final int UNFREEZE_REASON_UID_IDLE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UID_IDLE;
+    static final int UNFREEZE_REASON_STOP_SERVICE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_STOP_SERVICE;
+    static final int UNFREEZE_REASON_EXECUTING_SERVICE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_EXECUTING_SERVICE;
+    static final int UNFREEZE_REASON_RESTRICTION_CHANGE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE;
+    static final int UNFREEZE_REASON_COMPONENT_DISABLED =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED;
 
     @IntDef(prefix = {"UNFREEZE_REASON_"}, value = {
         UNFREEZE_REASON_NONE,
@@ -160,6 +203,16 @@
         UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE,
         UNFREEZE_REASON_BINDER_TXNS,
         UNFREEZE_REASON_FEATURE_FLAGS,
+        UNFREEZE_REASON_SHORT_FGS_TIMEOUT,
+        UNFREEZE_REASON_SYSTEM_INIT,
+        UNFREEZE_REASON_BACKUP,
+        UNFREEZE_REASON_SHELL,
+        UNFREEZE_REASON_REMOVE_TASK,
+        UNFREEZE_REASON_UID_IDLE,
+        UNFREEZE_REASON_STOP_SERVICE,
+        UNFREEZE_REASON_EXECUTING_SERVICE,
+        UNFREEZE_REASON_RESTRICTION_CHANGE,
+        UNFREEZE_REASON_COMPONENT_DISABLED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UnfreezeReason {}
@@ -1273,8 +1326,7 @@
         UidRecord uidRec = app.getUidRecord();
         if (uidRec != null && uidRec.isFrozen()) {
             uidRec.setFrozen(false);
-            mFreezeHandler.removeMessages(UID_FROZEN_STATE_CHANGED_MSG, app);
-            reportOneUidFrozenStateChanged(app.uid, false);
+            postUidFrozenMessage(uidRec.getUid(), false);
         }
 
         opt.setFreezerOverride(false);
@@ -1329,7 +1381,7 @@
         }
 
         try {
-            traceAppFreeze(app.processName, pid, false);
+            traceAppFreeze(app.processName, pid, reason);
             Process.setProcessFrozen(pid, app.uid, false);
 
             opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
@@ -1341,7 +1393,7 @@
         }
 
         if (!opt.isFrozen()) {
-            Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName);
+            Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName + " for " + reason);
 
             mFreezeHandler.sendMessage(
                     mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
@@ -1365,13 +1417,13 @@
      * The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app.
      * @param pid pid of the process to be unfrozen
      */
-    void unfreezeProcess(int pid, @OomAdjuster.OomAdjReason int reason) {
+    void unfreezeProcess(int pid, @OomAdjReason int reason) {
         synchronized (mFreezerLock) {
             ProcessRecord app = mFrozenProcesses.get(pid);
             if (app == null) {
                 return;
             }
-            Slog.d(TAG_AM, "quick sync unfreeze " + pid);
+            Slog.d(TAG_AM, "quick sync unfreeze " + pid + " for " +  reason);
             try {
                 freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
             } catch (RuntimeException e) {
@@ -1380,7 +1432,7 @@
             }
 
             try {
-                traceAppFreeze(app.processName, pid, false);
+                traceAppFreeze(app.processName, pid, reason);
                 Process.setProcessFrozen(pid, app.uid, false);
             } catch (Exception e) {
                 Slog.e(TAG_AM, "Unable to quick unfreeze " + pid);
@@ -1388,9 +1440,15 @@
         }
     }
 
-    private static void traceAppFreeze(String processName, int pid, boolean freeze) {
+    /**
+     * Trace app freeze status
+     * @param processName The name of the target process
+     * @param pid The pid of the target process
+     * @param reason UNFREEZE_REASON_XXX (>=0) for unfreezing and -1 for freezing
+     */
+    private static void traceAppFreeze(String processName, int pid, int reason) {
         Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_FREEZER_TRACK,
-                (freeze ? "Freeze " : "Unfreeze ") + processName + ":" + pid);
+                (reason < 0 ? "Freeze " : "Unfreeze ") + processName + ":" + pid + " " + reason);
     }
 
     /**
@@ -1409,8 +1467,7 @@
             UidRecord uidRec = app.getUidRecord();
             if (uidRec != null && uidRec.isFrozen()) {
                 uidRec.setFrozen(false);
-                mFreezeHandler.removeMessages(UID_FROZEN_STATE_CHANGED_MSG, app);
-                reportOneUidFrozenStateChanged(app.uid, false);
+                postUidFrozenMessage(uidRec.getUid(), false);
             }
 
             mFrozenProcesses.delete(app.getPid());
@@ -1540,12 +1597,12 @@
         public long mOrigAnonRss;
         public int mProcState;
         public int mOomAdj;
-        public @OomAdjuster.OomAdjReason int mOomAdjReason;
+        public @OomAdjReason int mOomAdjReason;
 
         SingleCompactionStats(long[] rss, CompactSource source, String processName,
                 long deltaAnonRss, long zramConsumed, long anonMemFreed, long origAnonRss,
                 long cpuTimeMillis, int procState, int oomAdj,
-                @OomAdjuster.OomAdjReason int oomAdjReason, int uid) {
+                @OomAdjReason int oomAdjReason, int uid) {
             mRssAfterCompaction = rss;
             mSourceType = source;
             mProcessName = processName;
@@ -1939,6 +1996,15 @@
         mAm.reportUidFrozenStateChanged(uids, frozenStates);
     }
 
+    private void postUidFrozenMessage(int uid, boolean frozen) {
+        final Integer uidObj = Integer.valueOf(uid);
+        mFreezeHandler.removeEqualMessages(UID_FROZEN_STATE_CHANGED_MSG, uidObj);
+
+        final int op = frozen ? 1 : 0;
+        mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(UID_FROZEN_STATE_CHANGED_MSG, op,
+                0, uidObj));
+    }
+
     private final class FreezeHandler extends Handler implements
             ProcLocksReader.ProcLocksReaderCallback {
         private FreezeHandler() {
@@ -1969,7 +2035,9 @@
                     reportUnfreeze(pid, frozenDuration, processName, reason);
                     break;
                 case UID_FROZEN_STATE_CHANGED_MSG:
-                    reportOneUidFrozenStateChanged(((ProcessRecord) msg.obj).uid, true);
+                    final boolean frozen = (msg.arg1 == 1);
+                    final int uid = (int) msg.obj;
+                    reportOneUidFrozenStateChanged(uid, frozen);
                     break;
                 case DEADLOCK_WATCHDOG_MSG:
                     try {
@@ -2063,7 +2131,7 @@
                 long unfreezeTime = opt.getFreezeUnfreezeTime();
 
                 try {
-                    traceAppFreeze(proc.processName, pid, true);
+                    traceAppFreeze(proc.processName, pid, -1);
                     Process.setProcessFrozen(pid, proc.uid, true);
 
                     opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
@@ -2080,8 +2148,8 @@
                 final UidRecord uidRec = proc.getUidRecord();
                 if (frozen && uidRec != null && uidRec.areAllProcessesFrozen()) {
                     uidRec.setFrozen(true);
-                    mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(
-                            UID_FROZEN_STATE_CHANGED_MSG, proc));
+
+                    postUidFrozenMessage(uidRec.getUid(), true);
                 }
             }
 
@@ -2127,7 +2195,7 @@
         private void reportUnfreeze(int pid, int frozenDuration, String processName,
                 @UnfreezeReason int reason) {
 
-            EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName);
+            EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName, reason);
 
             // See above for why we're not taking mPhenotypeFlagLock here
             if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
@@ -2201,32 +2269,52 @@
         }
     }
 
-    static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjuster.OomAdjReason int oomAdjReason) {
+    static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjReason int oomAdjReason) {
         switch (oomAdjReason) {
-            case OomAdjuster.OOM_ADJ_REASON_ACTIVITY:
+            case OOM_ADJ_REASON_ACTIVITY:
                 return UNFREEZE_REASON_ACTIVITY;
-            case OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER:
+            case OOM_ADJ_REASON_FINISH_RECEIVER:
                 return UNFREEZE_REASON_FINISH_RECEIVER;
-            case OomAdjuster.OOM_ADJ_REASON_START_RECEIVER:
+            case OOM_ADJ_REASON_START_RECEIVER:
                 return UNFREEZE_REASON_START_RECEIVER;
-            case OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE:
+            case OOM_ADJ_REASON_BIND_SERVICE:
                 return UNFREEZE_REASON_BIND_SERVICE;
-            case OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE:
+            case OOM_ADJ_REASON_UNBIND_SERVICE:
                 return UNFREEZE_REASON_UNBIND_SERVICE;
-            case OomAdjuster.OOM_ADJ_REASON_START_SERVICE:
+            case OOM_ADJ_REASON_START_SERVICE:
                 return UNFREEZE_REASON_START_SERVICE;
-            case OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER:
+            case OOM_ADJ_REASON_GET_PROVIDER:
                 return UNFREEZE_REASON_GET_PROVIDER;
-            case OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER:
+            case OOM_ADJ_REASON_REMOVE_PROVIDER:
                 return UNFREEZE_REASON_REMOVE_PROVIDER;
-            case OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY:
+            case OOM_ADJ_REASON_UI_VISIBILITY:
                 return UNFREEZE_REASON_UI_VISIBILITY;
-            case OomAdjuster.OOM_ADJ_REASON_ALLOWLIST:
+            case OOM_ADJ_REASON_ALLOWLIST:
                 return UNFREEZE_REASON_ALLOWLIST;
-            case OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN:
+            case OOM_ADJ_REASON_PROCESS_BEGIN:
                 return UNFREEZE_REASON_PROCESS_BEGIN;
-            case OomAdjuster.OOM_ADJ_REASON_PROCESS_END:
+            case OOM_ADJ_REASON_PROCESS_END:
                 return UNFREEZE_REASON_PROCESS_END;
+            case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
+                return UNFREEZE_REASON_SHORT_FGS_TIMEOUT;
+            case OOM_ADJ_REASON_SYSTEM_INIT:
+                return UNFREEZE_REASON_SYSTEM_INIT;
+            case OOM_ADJ_REASON_BACKUP:
+                return UNFREEZE_REASON_BACKUP;
+            case OOM_ADJ_REASON_SHELL:
+                return UNFREEZE_REASON_SHELL;
+            case OOM_ADJ_REASON_REMOVE_TASK:
+                return UNFREEZE_REASON_REMOVE_TASK;
+            case OOM_ADJ_REASON_UID_IDLE:
+                return UNFREEZE_REASON_UID_IDLE;
+            case OOM_ADJ_REASON_STOP_SERVICE:
+                return UNFREEZE_REASON_STOP_SERVICE;
+            case OOM_ADJ_REASON_EXECUTING_SERVICE:
+                return UNFREEZE_REASON_EXECUTING_SERVICE;
+            case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+                return UNFREEZE_REASON_RESTRICTION_CHANGE;
+            case OOM_ADJ_REASON_COMPONENT_DISABLED:
+                return UNFREEZE_REASON_COMPONENT_DISABLED;
             default:
                 return UNFREEZE_REASON_NONE;
         }
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index a1fcd42..d8cb094 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -16,6 +16,8 @@
 package com.android.server.am;
 
 import static android.Manifest.permission.GET_ANY_PROVIDER_TYPE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
 import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile;
 import static android.os.Process.PROC_CHAR;
 import static android.os.Process.PROC_OUT_LONG;
@@ -292,7 +294,7 @@
                     checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
                     final int verifiedAdj = cpr.proc.mState.getVerifiedAdj();
                     boolean success = mService.updateOomAdjLocked(cpr.proc,
-                            OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
+                            OOM_ADJ_REASON_GET_PROVIDER);
                     // XXX things have changed so updateOomAdjLocked doesn't actually tell us
                     // if the process has been successfully adjusted.  So to reduce races with
                     // it, we will check whether the process still exists.  Note that this doesn't
@@ -757,7 +759,7 @@
 
             // update the app's oom adj value and each provider's usage stats
             if (providersPublished) {
-                mService.updateOomAdjLocked(r, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
+                mService.updateOomAdjLocked(r, OOM_ADJ_REASON_GET_PROVIDER);
                 for (int i = 0, size = providers.size(); i < size; i++) {
                     ContentProviderHolder src = providers.get(i);
                     if (src == null || src.info == null || src.provider == null) {
@@ -835,8 +837,7 @@
             ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId);
             if (localCpr.hasExternalProcessHandles()) {
                 if (localCpr.removeExternalProcessHandleLocked(token)) {
-                    mService.updateOomAdjLocked(localCpr.proc,
-                            OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER);
+                    mService.updateOomAdjLocked(localCpr.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
                 } else {
                     Slog.e(TAG, "Attempt to remove content provider " + localCpr
                             + " with no external reference for token: " + token + ".");
@@ -1506,8 +1507,7 @@
             mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid,
                     cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
             if (updateOomAdj) {
-                mService.updateOomAdjLocked(conn.provider.proc,
-                        OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER);
+                mService.updateOomAdjLocked(conn.provider.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java
index 9ff2cd0..727d4df9 100644
--- a/services/core/java/com/android/server/am/DropboxRateLimiter.java
+++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java
@@ -22,7 +22,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
 
 /** Rate limiter for adding errors into dropbox. */
 public class DropboxRateLimiter {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a98571b..365dcd9 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -41,6 +41,29 @@
 import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
 import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
@@ -101,9 +124,9 @@
 import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 
-import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityThread;
 import android.app.AppProtoEnums;
 import android.app.ApplicationExitInfo;
@@ -141,8 +164,6 @@
 import com.android.server.wm.WindowProcessController;
 
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -154,32 +175,6 @@
 public class OomAdjuster {
     static final String TAG = "OomAdjuster";
 
-    static final int OOM_ADJ_REASON_NONE = 0;
-    static final int OOM_ADJ_REASON_ACTIVITY = 1;
-    static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
-    static final int OOM_ADJ_REASON_START_RECEIVER = 3;
-    static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
-    static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
-    static final int OOM_ADJ_REASON_START_SERVICE = 6;
-    static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
-    static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
-    static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
-    static final int OOM_ADJ_REASON_ALLOWLIST = 10;
-    static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
-    static final int OOM_ADJ_REASON_PROCESS_END = 12;
-    static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
-
-    @IntDef(prefix = {"OOM_ADJ_REASON_"},
-            value = {OOM_ADJ_REASON_NONE, OOM_ADJ_REASON_ACTIVITY, OOM_ADJ_REASON_FINISH_RECEIVER,
-                    OOM_ADJ_REASON_START_RECEIVER, OOM_ADJ_REASON_BIND_SERVICE,
-                    OOM_ADJ_REASON_UNBIND_SERVICE, OOM_ADJ_REASON_START_SERVICE,
-                    OOM_ADJ_REASON_GET_PROVIDER, OOM_ADJ_REASON_REMOVE_PROVIDER,
-                    OOM_ADJ_REASON_UI_VISIBILITY, OOM_ADJ_REASON_ALLOWLIST,
-                    OOM_ADJ_REASON_PROCESS_BEGIN, OOM_ADJ_REASON_PROCESS_END,
-                    OOM_ADJ_REASON_SHORT_FGS_TIMEOUT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface OomAdjReason {}
-
     public static final int oomAdjReasonToProto(@OomAdjReason int oomReason) {
         switch (oomReason) {
             case OOM_ADJ_REASON_NONE:
@@ -210,6 +205,24 @@
                 return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END;
             case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
                 return AppProtoEnums.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+            case OOM_ADJ_REASON_SYSTEM_INIT:
+                return AppProtoEnums.OOM_ADJ_REASON_SYSTEM_INIT;
+            case OOM_ADJ_REASON_BACKUP:
+                return AppProtoEnums.OOM_ADJ_REASON_BACKUP;
+            case OOM_ADJ_REASON_SHELL:
+                return AppProtoEnums.OOM_ADJ_REASON_SHELL;
+            case OOM_ADJ_REASON_REMOVE_TASK:
+                return AppProtoEnums.OOM_ADJ_REASON_REMOVE_TASK;
+            case OOM_ADJ_REASON_UID_IDLE:
+                return AppProtoEnums.OOM_ADJ_REASON_UID_IDLE;
+            case OOM_ADJ_REASON_STOP_SERVICE:
+                return AppProtoEnums.OOM_ADJ_REASON_STOP_SERVICE;
+            case OOM_ADJ_REASON_EXECUTING_SERVICE:
+                return AppProtoEnums.OOM_ADJ_REASON_EXECUTING_SERVICE;
+            case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+                return AppProtoEnums.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+            case OOM_ADJ_REASON_COMPONENT_DISABLED:
+                return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED;
             default:
                 return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO;
         }
@@ -246,6 +259,24 @@
                 return OOM_ADJ_REASON_METHOD + "_processEnd";
             case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
                 return OOM_ADJ_REASON_METHOD + "_shortFgs";
+            case OOM_ADJ_REASON_SYSTEM_INIT:
+                return OOM_ADJ_REASON_METHOD + "_systemInit";
+            case OOM_ADJ_REASON_BACKUP:
+                return OOM_ADJ_REASON_METHOD + "_backup";
+            case OOM_ADJ_REASON_SHELL:
+                return OOM_ADJ_REASON_METHOD + "_shell";
+            case OOM_ADJ_REASON_REMOVE_TASK:
+                return OOM_ADJ_REASON_METHOD + "_removeTask";
+            case OOM_ADJ_REASON_UID_IDLE:
+                return OOM_ADJ_REASON_METHOD + "_uidIdle";
+            case OOM_ADJ_REASON_STOP_SERVICE:
+                return OOM_ADJ_REASON_METHOD + "_stopService";
+            case OOM_ADJ_REASON_EXECUTING_SERVICE:
+                return OOM_ADJ_REASON_METHOD + "_executingService";
+            case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+                return OOM_ADJ_REASON_METHOD + "_restrictionChange";
+            case OOM_ADJ_REASON_COMPONENT_DISABLED:
+                return OOM_ADJ_REASON_METHOD + "_componentDisabled";
             default:
                 return "_unknown";
         }
@@ -874,8 +905,7 @@
     }
 
     @GuardedBy("mService")
-    private void performUpdateOomAdjPendingTargetsLocked(
-            @OomAdjuster.OomAdjReason int oomAdjReason) {
+    private void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
@@ -3453,7 +3483,7 @@
     }
 
     @GuardedBy("mService")
-    void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
+    void unfreezeTemporarily(ProcessRecord app, @OomAdjReason int reason) {
         if (!mCachedAppOptimizer.useFreezer()) {
             return;
         }
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 24cc533..f233107 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import android.app.ActivityManagerInternal.OomAdjReason;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -51,7 +53,7 @@
     /**
      * Last oom adjust change reason for this app.
      */
-    @GuardedBy("mProcLock") private @OomAdjuster.OomAdjReason int mLastOomAdjChangeReason;
+    @GuardedBy("mProcLock") private @OomAdjReason int mLastOomAdjChangeReason;
 
     /**
      * The most recent compaction action performed for this app.
@@ -139,12 +141,12 @@
     }
 
     @GuardedBy("mProcLock")
-    void setLastOomAdjChangeReason(@OomAdjuster.OomAdjReason int reason) {
+    void setLastOomAdjChangeReason(@OomAdjReason int reason) {
         mLastOomAdjChangeReason = reason;
     }
 
     @GuardedBy("mProcLock")
-    @OomAdjuster.OomAdjReason
+    @OomAdjReason
     int getLastOomAdjChangeReason() {
         return mLastOomAdjChangeReason;
     }
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 70a696c..ca41f42 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -51,7 +51,7 @@
 import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.os.anr.AnrLatencyTracker;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b1322ef..312f98a 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -19,6 +19,8 @@
 import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
 import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
 import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
@@ -2875,7 +2877,7 @@
                     reasonCode, subReason, reason, !doFreeze /* async */);
         }
         killAppZygotesLocked(packageName, appId, userId, false /* force */);
-        mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+        mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
         if (doFreeze) {
             freezePackageCgroup(packageUID, false);
         }
@@ -5140,7 +5142,7 @@
                 }
             });
             /* Will be a no-op if nothing pending */
-            mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_RESTRICTION_CHANGE);
         }
     }
 
@@ -5203,6 +5205,17 @@
     }
 
     /**
+     * Called by ActivityManagerService when a recoverable native crash occurs.
+     */
+    @GuardedBy("mService")
+    void noteAppRecoverableCrash(final ProcessRecord app) {
+        if (DEBUG_PROCESSES) {
+            Slog.i(TAG, "note: " + app + " has a recoverable native crash");
+        }
+        mAppExitInfoTracker.scheduleNoteAppRecoverableCrash(app);
+    }
+
+    /**
      * Called by ActivityManagerService when it decides to kill an application process.
      */
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index afae623..ffb40ee 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -577,7 +579,9 @@
         processName = _processName;
         sdkSandboxClientAppPackage = _sdkSandboxClientAppPackage;
         if (isSdkSandbox) {
-            sdkSandboxClientAppVolumeUuid = getClientInfoForSdkSandbox().volumeUuid;
+            final ApplicationInfo clientInfo = getClientInfoForSdkSandbox();
+            sdkSandboxClientAppVolumeUuid = clientInfo != null
+                    ? clientInfo.volumeUuid : null;
         } else {
             sdkSandboxClientAppVolumeUuid = null;
         }
@@ -1450,7 +1454,7 @@
             }
             mService.updateLruProcessLocked(this, activityChange, null /* client */);
             if (updateOomAdj) {
-                mService.updateOomAdjLocked(this, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+                mService.updateOomAdjLocked(this, OOM_ADJ_REASON_ACTIVITY);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 71d5d39..ab71acd 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
 import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_ACTIVITY;
@@ -613,7 +614,7 @@
     void forceProcessStateUpTo(int newState) {
         if (mRepProcState > newState) {
             synchronized (mProcLock) {
-                mRepProcState = newState;
+                setReportedProcState(newState);
                 setCurProcState(newState);
                 setCurRawProcState(newState);
             }
@@ -766,7 +767,7 @@
             Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
                     + " for pid=" + mApp.getPid());
         }
-        mService.updateOomAdjLocked(mApp, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+        mService.updateOomAdjLocked(mApp, OOM_ADJ_REASON_UI_VISIBILITY);
     }
 
     @GuardedBy({"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 22e2c9f..8c227f5 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -100,7 +100,6 @@
         DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_SWCODEC_NATIVE,
-        DeviceConfig.NAMESPACE_TETHERING,
         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE,
         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 81ba4b8..a110169 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -34,6 +34,7 @@
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
 import static android.app.AppOpsManager.OP_FLAG_SELF;
 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
@@ -42,6 +43,7 @@
 import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
 import static android.app.AppOpsManager.OP_VIBRATE;
 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
@@ -3027,17 +3029,29 @@
                     packageName);
         }
 
-        // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
-        // purposes and not as a check, also make sure that the caller is allowed to access
-        // the data gated by OP_RECORD_AUDIO.
+        // As a special case for OP_RECORD_AUDIO_HOTWORD, OP_RECEIVE_AMBIENT_TRIGGER_AUDIO and
+        // OP_RECORD_AUDIO_SANDBOXED which we use only for attribution purposes and not as a check,
+        // also make sure that the caller is allowed to access the data gated by OP_RECORD_AUDIO.
         //
         // TODO: Revert this change before Android 12.
-        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
-            int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+        int result = MODE_DEFAULT;
+        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO
+                || code == OP_RECORD_AUDIO_SANDBOXED) {
+            result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+            // Check result
             if (result != AppOpsManager.MODE_ALLOWED) {
                 return new SyncNotedAppOp(result, code, attributionTag, packageName);
             }
         }
+        // As a special case for OP_CAMERA_SANDBOXED.
+        if (code == OP_CAMERA_SANDBOXED) {
+            result = checkOperation(OP_CAMERA, uid, packageName);
+            // Check result
+            if (result != AppOpsManager.MODE_ALLOWED) {
+                return new SyncNotedAppOp(result, code, attributionTag, packageName);
+            }
+        }
+
         return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
                 Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
                 shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 7c8e6df..5127d26 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -19,6 +19,8 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
 
@@ -519,6 +521,9 @@
 
         try {
             mStatusBarService.onBiometricHelp(sensorIdToModality(sensorId), message);
+            final int aAcquiredInfo = acquiredInfo == FINGERPRINT_ACQUIRED_VENDOR
+                    ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquiredInfo;
+            mClientReceiver.onAcquired(aAcquiredInfo, message);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 7ae31b2..50d375c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -212,6 +212,8 @@
         // 1) Authenticated == true
         // 2) Error occurred
         // 3) Authenticated == false
+        // 4) onLockout
+        // 5) onLockoutTimed
         mCallback.onClientFinished(this, true /* success */);
     }
 
@@ -304,11 +306,7 @@
         PerformanceTracker.getInstanceForSensorId(getSensorId())
                 .incrementTimedLockoutForUser(getTargetUserId());
 
-        try {
-            getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception", e);
-        }
+        onError(error, 0 /* vendorCode */);
     }
 
     @Override
@@ -323,10 +321,6 @@
         PerformanceTracker.getInstanceForSensorId(getSensorId())
                 .incrementPermanentLockoutForUser(getTargetUserId());
 
-        try {
-            getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception", e);
-        }
+        onError(error, 0 /* vendorCode */);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 1a53fec..c5037b7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -465,7 +465,7 @@
                         BaseClientMonitor clientMonitor,
                         boolean success) {
                     mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
-                            sensorId, requestId, success);
+                            sensorId, requestId, client.wasAuthSuccessful());
                 }
             });
         });
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 128ef0b..6c26e2b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -413,6 +413,11 @@
                                 Slog.e(TAG, "Remote exception in onAuthenticationAcquired()", e);
                             }
                         }
+
+                        @Override
+                        public void onAuthenticationHelp(int acquireInfo, CharSequence helpString) {
+                            onAuthenticationAcquired(acquireInfo);
+                        }
                     };
 
             return biometricPrompt.authenticateForOperation(
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index 2ac2833..c039a83 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -334,8 +334,8 @@
     }
 
     private int installCert(ShellCommand shell) throws SystemFontException {
-        if (!(Build.IS_USERDEBUG || Build.IS_ENG)) {
-            throw new SecurityException("Only userdebug/eng device can add debug certificate");
+        if (!Build.IS_DEBUGGABLE) {
+            throw new SecurityException("Only debuggable device can add debug certificate");
         }
         if (Binder.getCallingUid() != Process.ROOT_UID) {
             throw new SecurityException("Only root can add debug certificate");
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 43e346a..2d40661 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -323,7 +323,7 @@
      */
     void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId,
             LocaleList locales) {
-        String installingPackageName = getInstallingPackageName(appPackageName);
+        String installingPackageName = getInstallingPackageName(appPackageName, userId);
         if (installingPackageName != null) {
             Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED,
                     appPackageName, locales);
@@ -464,7 +464,7 @@
      * Checks if the calling app is the installer of the app whose locale changed.
      */
     private boolean isCallerInstaller(String appPackageName, int userId) {
-        String installingPackageName = getInstallingPackageName(appPackageName);
+        String installingPackageName = getInstallingPackageName(appPackageName, userId);
         if (installingPackageName != null) {
             // Get the uid of installer-on-record to compare with the calling uid.
             int installerUid = getPackageUid(installingPackageName, userId);
@@ -513,10 +513,11 @@
     }
 
     @Nullable
-    String getInstallingPackageName(String packageName) {
+    String getInstallingPackageName(String packageName, int userId) {
         try {
-            return mContext.getPackageManager()
-                    .getInstallSourceInfo(packageName).getInstallingPackageName();
+            return mContext.createContextAsUser(UserHandle.of(userId), /* flags= */
+                    0).getPackageManager().getInstallSourceInfo(
+                    packageName).getInstallingPackageName();
         } catch (PackageManager.NameNotFoundException e) {
             Slog.w(TAG, "Package not found " + packageName);
         }
diff --git a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
index 215c653..373d355 100644
--- a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
+++ b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
@@ -152,9 +152,10 @@
     void onPackageUpdateFinished(String packageName, int uid) {
         try {
             if ((!mUpdatedApps.contains(packageName)) && isUpdatedSystemApp(packageName)) {
+                int userId = UserHandle.getUserId(uid);
                 // If a system app is updated, verify that it has an installer-on-record.
                 String installingPackageName = mLocaleManagerService.getInstallingPackageName(
-                        packageName);
+                        packageName, userId);
                 if (installingPackageName == null) {
                     // We want to broadcast the locales info to the installer.
                     // If this app does not have an installer then do nothing.
@@ -162,7 +163,6 @@
                 }
 
                 try {
-                    int userId = UserHandle.getUserId(uid);
                     // Fetch the app-specific locales.
                     // If non-empty then send the info to the installer.
                     LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 653b718..5f78374 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -519,17 +519,24 @@
         BroadcastReceiver btReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())
-                        || BluetoothAdapter.ACTION_BLE_STATE_CHANGED.equals(
-                        intent.getAction())) {
+                if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
                     sendBtSettingUpdate(/* forceUpdate= */ false);
                 }
             }
         };
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
-        filter.addAction(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
         mContext.registerReceiver(btReceiver, filter);
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE),
+                /* notifyForDescendants= */ false,
+                new ContentObserver(/* handler= */ null) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        sendBtSettingUpdate(/* forceUpdate= */ false);
+                    }
+                }, UserHandle.USER_ALL);
     }
 
     /**
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 273afcc..dff02bf 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -15,44 +15,53 @@
  */
 package com.android.server.notification;
 
+import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
+import static android.app.Notification.FLAG_GROUP_SUMMARY;
+import static android.app.Notification.FLAG_LOCAL_ONLY;
+import static android.app.Notification.FLAG_NO_CLEAR;
+import static android.app.Notification.FLAG_ONGOING_EVENT;
+
+import android.annotation.NonNull;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Map;
 
 /**
  * NotificationManagerService helper for auto-grouping notifications.
  */
 public class GroupHelper {
     private static final String TAG = "GroupHelper";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     protected static final String AUTOGROUP_KEY = "ranker_group";
 
+    // Flags that all autogroup summaries have
+    protected static final int BASE_FLAGS =
+            FLAG_AUTOGROUP_SUMMARY | FLAG_GROUP_SUMMARY | FLAG_LOCAL_ONLY;
+    // Flag that autogroup summaries inherits if all children have the flag
+    private static final int ALL_CHILDREN_FLAG = FLAG_AUTO_CANCEL;
+    // Flags that autogroup summaries inherits if any child has them
+    private static final int ANY_CHILDREN_FLAGS = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR;
+
     private final Callback mCallback;
     private final int mAutoGroupAtCount;
 
-    // count the number of ongoing notifications per group
-    // userId|packageName -> (set of ongoing notifications that aren't in an app group)
-    final ArrayMap<String, ArraySet<String>>
-            mOngoingGroupCount = new ArrayMap<>();
-
-    // Map of user : <Map of package : notification keys>. Only contains notifications that are not
-    // grouped by the app (aka no group or sort key).
-    Map<Integer, Map<String, LinkedHashSet<String>>> mUngroupedNotifications = new HashMap<>();
+    // Only contains notifications that are not explicitly grouped by the app (aka no group or
+    // sort key).
+    // userId|packageName -> (keys of notifications that aren't in an explicit app group -> flags)
+    @GuardedBy("mUngroupedNotifications")
+    private final ArrayMap<String, ArrayMap<String, Integer>> mUngroupedNotifications
+            = new ArrayMap<>();
 
     public GroupHelper(int autoGroupAtCount, Callback callback) {
         mAutoGroupAtCount = autoGroupAtCount;
-        mCallback = callback;
+        mCallback =  callback;
     }
 
     private String generatePackageKey(int userId, String pkg) {
@@ -60,69 +69,30 @@
     }
 
     @VisibleForTesting
-    protected int getOngoingGroupCount(int userId, String pkg) {
-        String key = generatePackageKey(userId, pkg);
-        return mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0)).size();
+    @GuardedBy("mUngroupedNotifications")
+    protected int getAutogroupSummaryFlags(@NonNull final ArrayMap<String, Integer> children) {
+        boolean allChildrenHasFlag = children.size() > 0;
+        int anyChildFlagSet = 0;
+        for (int i = 0; i < children.size(); i++) {
+            if (!hasAnyFlag(children.valueAt(i), ALL_CHILDREN_FLAG)) {
+                allChildrenHasFlag = false;
+            }
+            if (hasAnyFlag(children.valueAt(i), ANY_CHILDREN_FLAGS)) {
+                anyChildFlagSet |= (children.valueAt(i) & ANY_CHILDREN_FLAGS);
+            }
+        }
+        return BASE_FLAGS | (allChildrenHasFlag ? ALL_CHILDREN_FLAG : 0) | anyChildFlagSet;
     }
 
-    private void updateOngoingGroupCount(StatusBarNotification sbn, boolean add) {
-        if (sbn.getNotification().isGroupSummary()) {
-            return;
-        }
-        String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
-        ArraySet<String> notifications = mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0));
-        if (add) {
-            notifications.add(sbn.getKey());
-            mOngoingGroupCount.put(key, notifications);
-        } else {
-            notifications.remove(sbn.getKey());
-            // we don't need to put it back if it is default
-        }
-
-        boolean needsOngoingFlag = notifications.size() > 0;
-        mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), needsOngoingFlag);
-    }
-
-    public void onNotificationUpdated(StatusBarNotification childSbn) {
-        updateOngoingGroupCount(childSbn, childSbn.isOngoing() && !childSbn.isAppGroup());
+    private boolean hasAnyFlag(int flags, int mask) {
+        return (flags & mask) != 0;
     }
 
     public void onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) {
         try {
-            updateOngoingGroupCount(sbn, sbn.isOngoing() && !sbn.isAppGroup());
-
-            List<String> notificationsToGroup = new ArrayList<>();
             if (!sbn.isAppGroup()) {
-                // Not grouped by the app, add to the list of notifications for the app;
-                // send grouping update if app exceeds the autogrouping limit.
-                synchronized (mUngroupedNotifications) {
-                    Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
-                            = mUngroupedNotifications.get(sbn.getUserId());
-                    if (ungroupedNotificationsByUser == null) {
-                        ungroupedNotificationsByUser = new HashMap<>();
-                    }
-                    mUngroupedNotifications.put(sbn.getUserId(), ungroupedNotificationsByUser);
-                    LinkedHashSet<String> notificationsForPackage
-                            = ungroupedNotificationsByUser.get(sbn.getPackageName());
-                    if (notificationsForPackage == null) {
-                        notificationsForPackage = new LinkedHashSet<>();
-                    }
-
-                    notificationsForPackage.add(sbn.getKey());
-                    ungroupedNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage);
-
-                    if (notificationsForPackage.size() >= mAutoGroupAtCount
-                            || autogroupSummaryExists) {
-                        notificationsToGroup.addAll(notificationsForPackage);
-                    }
-                }
-                if (notificationsToGroup.size() > 0) {
-                    adjustAutogroupingSummary(sbn.getUserId(), sbn.getPackageName(),
-                            notificationsToGroup.get(0), true);
-                    adjustNotificationBundling(notificationsToGroup, true);
-                }
+                maybeGroup(sbn, autogroupSummaryExists);
             } else {
-                // Grouped, but not by us. Send updates to un-autogroup, if we grouped it.
                 maybeUngroup(sbn, false, sbn.getUserId());
             }
 
@@ -133,7 +103,6 @@
 
     public void onNotificationRemoved(StatusBarNotification sbn) {
         try {
-            updateOngoingGroupCount(sbn, false);
             maybeUngroup(sbn, true, sbn.getUserId());
         } catch (Exception e) {
             Slog.e(TAG, "Error processing canceled notification", e);
@@ -141,70 +110,114 @@
     }
 
     /**
-     * Un-autogroups notifications that are now grouped by the app.
+     * A non-app grouped notification has been added or updated
+     * Evaluate if:
+     * (a) an existing autogroup summary needs updated flags
+     * (b) a new autogroup summary needs to be added with correct flags
+     * (c) other non-app grouped children need to be moved to the autogroup
+     *
+     * And stores the list of upgrouped notifications & their flags
+     */
+    private void maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) {
+        int flags = 0;
+        List<String> notificationsToGroup = new ArrayList<>();
+        synchronized (mUngroupedNotifications) {
+            String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
+            final ArrayMap<String, Integer> children =
+                    mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
+
+            children.put(sbn.getKey(), sbn.getNotification().flags);
+            mUngroupedNotifications.put(key, children);
+
+            if (children.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
+                flags = getAutogroupSummaryFlags(children);
+                notificationsToGroup.addAll(children.keySet());
+            }
+        }
+        if (notificationsToGroup.size() > 0) {
+            if (autogroupSummaryExists) {
+                mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), flags);
+            } else {
+                mCallback.addAutoGroupSummary(
+                        sbn.getUserId(), sbn.getPackageName(), sbn.getKey(), flags);
+            }
+            for (String key : notificationsToGroup) {
+                mCallback.addAutoGroup(key);
+            }
+        }
+    }
+
+    /**
+     * A notification was added that's app grouped, or a notification was removed.
+     * Evaluate whether:
+     * (a) an existing autogroup summary needs updated flags
+     * (b) if we need to remove our autogroup overlay for this notification
+     * (c) we need to remove the autogroup summary
+     *
+     * And updates the internal state of un-app-grouped notifications and their flags
      */
     private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) {
-        List<String> notificationsToUnAutogroup = new ArrayList<>();
         boolean removeSummary = false;
+        int summaryFlags = 0;
+        boolean updateSummaryFlags = false;
+        boolean removeAutogroupOverlay = false;
         synchronized (mUngroupedNotifications) {
-            Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
-                    = mUngroupedNotifications.get(sbn.getUserId());
-            if (ungroupedNotificationsByUser == null || ungroupedNotificationsByUser.size() == 0) {
+            String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
+            final ArrayMap<String, Integer> children =
+                    mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
+            if (children.size() == 0) {
                 return;
             }
-            LinkedHashSet<String> notificationsForPackage
-                    = ungroupedNotificationsByUser.get(sbn.getPackageName());
-            if (notificationsForPackage == null || notificationsForPackage.size() == 0) {
-                return;
-            }
-            if (notificationsForPackage.remove(sbn.getKey())) {
-                if (!notificationGone) {
-                    // Add the current notification to the ungrouping list if it still exists.
-                    notificationsToUnAutogroup.add(sbn.getKey());
+
+            // if this notif was autogrouped and now isn't
+            if (children.containsKey(sbn.getKey())) {
+                // if this notification was contributing flags that aren't covered by other
+                // children to the summary, reevaluate flags for the summary
+                int flags = children.remove(sbn.getKey());
+                // this
+                if (hasAnyFlag(flags, ANY_CHILDREN_FLAGS)) {
+                    updateSummaryFlags = true;
+                    summaryFlags = getAutogroupSummaryFlags(children);
                 }
-            }
-            // If the status change of this notification has brought the number of loose
-            // notifications to zero, remove the summary and un-autogroup.
-            if (notificationsForPackage.size() == 0) {
-                ungroupedNotificationsByUser.remove(sbn.getPackageName());
-                removeSummary = true;
+                // if this notification still exists and has an autogroup overlay, but is now
+                // grouped by the app, clear the overlay
+                if (!notificationGone && sbn.getOverrideGroupKey() != null) {
+                    removeAutogroupOverlay = true;
+                }
+
+                // If there are no more children left to autogroup, remove the summary
+                if (children.size() == 0) {
+                    removeSummary = true;
+                }
             }
         }
         if (removeSummary) {
-            adjustAutogroupingSummary(userId, sbn.getPackageName(), null, false);
-        }
-        if (notificationsToUnAutogroup.size() > 0) {
-            adjustNotificationBundling(notificationsToUnAutogroup, false);
-        }
-    }
-
-    private void adjustAutogroupingSummary(int userId, String packageName, String triggeringKey,
-            boolean summaryNeeded) {
-        if (summaryNeeded) {
-            mCallback.addAutoGroupSummary(userId, packageName, triggeringKey,
-                    getOngoingGroupCount(userId, packageName) > 0);
+            mCallback.removeAutoGroupSummary(userId, sbn.getPackageName());
         } else {
-            mCallback.removeAutoGroupSummary(userId, packageName);
+            if (updateSummaryFlags) {
+                mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), summaryFlags);
+            }
+        }
+        if (removeAutogroupOverlay) {
+            mCallback.removeAutoGroup(sbn.getKey());
         }
     }
 
-    private void adjustNotificationBundling(List<String> keys, boolean group) {
-        for (String key : keys) {
-            if (DEBUG) Log.i(TAG, "Sending grouping adjustment for: " + key + " group? " + group);
-            if (group) {
-                mCallback.addAutoGroup(key);
-            } else {
-                mCallback.removeAutoGroup(key);
-            }
+    @VisibleForTesting
+    int getNotGroupedByAppCount(int userId, String pkg) {
+        synchronized (mUngroupedNotifications) {
+            String key = generatePackageKey(userId, pkg);
+            final ArrayMap<String, Integer> children =
+                    mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
+            return children.size();
         }
     }
 
     protected interface Callback {
         void addAutoGroup(String key);
         void removeAutoGroup(String key);
-        void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
-                boolean needsOngoingFlag);
+        void addAutoGroupSummary(int userId, String pkg, String triggeringKey, int flags);
         void removeAutoGroupSummary(int user, String pkg);
-        void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag);
+        void updateAutogroupSummary(int userId, String pkg, int flags);
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5d81dda..1301cd47 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -319,6 +319,7 @@
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.policy.PermissionPolicyInternal;
+import com.android.server.powerstats.StatsPullAtomCallbackImpl;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.utils.Slogf;
@@ -902,11 +903,11 @@
      * has the same flag. It will delete the flag otherwise
      * @param userId user id of the autogroup summary
      * @param pkg package of the autogroup summary
-     * @param needsOngoingFlag true if the group has at least one ongoing notification
+     * @param flags the new flags for this summary
      * @param isAppForeground true if the app is currently in the foreground.
      */
     @GuardedBy("mNotificationLock")
-    protected void updateAutobundledSummaryFlags(int userId, String pkg, boolean needsOngoingFlag,
+    protected void updateAutobundledSummaryFlags(int userId, String pkg, int flags,
             boolean isAppForeground) {
         ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
         if (summaries == null) {
@@ -921,13 +922,8 @@
             return;
         }
         int oldFlags = summary.getSbn().getNotification().flags;
-        if (needsOngoingFlag) {
-            summary.getSbn().getNotification().flags |= FLAG_ONGOING_EVENT;
-        } else {
-            summary.getSbn().getNotification().flags &= ~FLAG_ONGOING_EVENT;
-        }
-
-        if (summary.getSbn().getNotification().flags != oldFlags) {
+        if (oldFlags != flags) {
+            summary.getSbn().getNotification().flags = flags;
             mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
                     SystemClock.elapsedRealtime()));
         }
@@ -2684,9 +2680,14 @@
 
             @Override
             public void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
-                    boolean needsOngoingFlag) {
-                NotificationManagerService.this.addAutoGroupSummary(
-                        userId, pkg, triggeringKey, needsOngoingFlag);
+                    int flags) {
+                NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey, flags);
+                if (r != null) {
+                    final boolean isAppForeground =
+                            mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
+                    mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
+                            SystemClock.elapsedRealtime()));
+                }
             }
 
             @Override
@@ -2697,11 +2698,11 @@
             }
 
             @Override
-            public void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag) {
+            public void updateAutogroupSummary(int userId, String pkg, int flags) {
                 boolean isAppForeground = pkg != null
                         && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
                 synchronized (mNotificationLock) {
-                    updateAutobundledSummaryFlags(userId, pkg, needsOngoingFlag, isAppForeground);
+                    updateAutobundledSummaryFlags(userId, pkg, flags, isAppForeground);
                 }
             }
         });
@@ -5961,19 +5962,6 @@
         r.addAdjustment(adjustment);
     }
 
-    @VisibleForTesting
-    void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
-            boolean needsOngoingFlag) {
-        NotificationRecord r = createAutoGroupSummary(
-                userId, pkg, triggeringKey, needsOngoingFlag);
-        if (r != null) {
-            final boolean isAppForeground =
-                    mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
-            mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
-                    SystemClock.elapsedRealtime()));
-        }
-    }
-
     // Clears the 'fake' auto-group summary.
     @VisibleForTesting
     @GuardedBy("mNotificationLock")
@@ -5997,7 +5985,7 @@
 
     // Creates a 'fake' summary for a package that has exceeded the solo-notification limit.
     NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey,
-            boolean needsOngoingFlag) {
+            int flagsToSet) {
         NotificationRecord summaryRecord = null;
         boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
         synchronized (mNotificationLock) {
@@ -6007,7 +5995,6 @@
                 // adjustment will post a summary if needed.
                 return null;
             }
-            NotificationChannel channel = notificationRecord.getChannel();
             final StatusBarNotification adjustedSbn = notificationRecord.getSbn();
             userId = adjustedSbn.getUser().getIdentifier();
             int uid =  adjustedSbn.getUid();
@@ -6030,11 +6017,8 @@
                                 .setGroupSummary(true)
                                 .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
                                 .setGroup(GroupHelper.AUTOGROUP_KEY)
-                                .setFlag(FLAG_AUTOGROUP_SUMMARY, true)
-                                .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
-                                .setFlag(FLAG_ONGOING_EVENT, needsOngoingFlag)
+                                .setFlag(flagsToSet, true)
                                 .setColor(adjustedSbn.getNotification().color)
-                                .setLocalOnly(true)
                                 .build();
                 summaryNotification.extras.putAll(extras);
                 Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg);
@@ -6372,6 +6356,7 @@
      * The private API only accessible to the system process.
      */
     private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
+
         @Override
         public NotificationChannel getNotificationChannel(String pkg, int uid, String
                 channelId) {
@@ -7827,18 +7812,17 @@
                     if (notification.getSmallIcon() != null) {
                         StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
                         mListeners.notifyPostedLocked(r, old);
-                        if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup()))
-                                && !isCritical(r)) {
-                            mHandler.post(() -> {
-                                synchronized (mNotificationLock) {
-                                    mGroupHelper.onNotificationPosted(
-                                            n, hasAutoGroupSummaryLocked(n));
-                                }
-                            });
-                        } else if (oldSbn != null) {
-                            final NotificationRecord finalRecord = r;
-                            mHandler.post(() ->
-                                    mGroupHelper.onNotificationUpdated(finalRecord.getSbn()));
+                        if (oldSbn == null
+                                || !Objects.equals(oldSbn.getGroup(), n.getGroup())
+                                || oldSbn.getNotification().flags != n.getNotification().flags) {
+                            if (!isCritical(r)) {
+                                mHandler.post(() -> {
+                                    synchronized (mNotificationLock) {
+                                        mGroupHelper.onNotificationPosted(
+                                                n, hasAutoGroupSummaryLocked(n));
+                                    }
+                                });
+                            }
                         }
                     } else {
                         Slog.e(TAG, "Not posting notification without small icon: " + notification);
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 35b94e7..88d23ce 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -27,6 +27,7 @@
 import android.service.notification.IConditionProvider;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeDiff;
 import android.util.Log;
 import android.util.Slog;
 
@@ -146,13 +147,13 @@
 
     public static void traceConfig(String reason, ZenModeConfig oldConfig,
             ZenModeConfig newConfig) {
-        ZenModeConfig.Diff diff = ZenModeConfig.diff(oldConfig, newConfig);
-        if (diff.isEmpty()) {
+        ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig);
+        if (diff == null || !diff.hasDiff()) {
             append(TYPE_CONFIG, reason + " no changes");
         } else {
             append(TYPE_CONFIG, reason
                     + ",\n" + (newConfig != null ? newConfig.toString() : null)
-                    + ",\n" + ZenModeConfig.diff(oldConfig, newConfig));
+                    + ",\n" + diff);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 596e9b9..69ef3f7 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -994,7 +994,7 @@
                     reconciledPackages = ReconcilePackageUtils.reconcilePackages(
                             requests, Collections.unmodifiableMap(mPm.mPackages),
                             versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
-                            mPm.mSettings);
+                            mPm.mSettings, mContext);
                 } catch (ReconcileFailure e) {
                     for (InstallRequest request : requests) {
                         request.setError("Reconciliation failed...", e);
@@ -3930,7 +3930,7 @@
                                 mPm.mPackages, Collections.singletonMap(pkgName,
                                         mPm.getSettingsVersionForPackage(parsedPackage)),
                                 mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
-                                mPm.mSettings);
+                                mPm.mSettings, mContext);
                 if ((scanFlags & SCAN_AS_APEX) == 0) {
                     appIdCreated = optimisticallyRegisterAppId(installRequest);
                 } else {
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 5312ae6..e3c97e9 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY;
@@ -23,19 +24,24 @@
 import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
 import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
 
+import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
 import android.os.SystemProperties;
+import android.permission.PermissionManager;
 import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedUsesPermission;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.utils.WatchedLongSparseArray;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -54,7 +60,7 @@
             Map<String, AndroidPackage> allPackages,
             Map<String, Settings.VersionInfo> versionInfos,
             SharedLibrariesImpl sharedLibraries,
-            KeySetManagerService ksms, Settings settings)
+            KeySetManagerService ksms, Settings settings, Context context)
             throws ReconcileFailure {
         final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
 
@@ -143,11 +149,11 @@
                 } else {
                     if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
                         throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
-                                "Package " + parsedPackage.getPackageName()
+                                "Package " + installPackageName
                                         + " upgrade keys do not match the previously installed"
                                         + " version");
                     } else {
-                        String msg = "System package " + parsedPackage.getPackageName()
+                        String msg = "System package " + installPackageName
                                 + " signature changed; retaining data.";
                         PackageManagerService.reportSettingsProblem(Log.WARN, msg);
                     }
@@ -168,11 +174,42 @@
                         removeAppKeySetData = true;
                     }
 
-                    // if this is is a sharedUser, check to see if the new package is signed by a
-                    // newer
-                    // signing certificate than the existing one, and if so, copy over the new
+                    // if this is a sharedUser, check to see if the new package is signed by a
+                    // newer signing certificate than the existing one, and if so, copy over the new
                     // details
                     if (sharedUserSetting != null) {
+                        if (!parsedPackage.isTestOnly() && sharedUserSetting.isPrivileged()
+                                && !signatureCheckPs.isSystem()) {
+                            final List<ParsedUsesPermission> usesPermissions =
+                                    parsedPackage.getUsesPermissions();
+                            final List<String> usesPrivilegedPermissions = new ArrayList<>();
+                            final PermissionManager permissionManager = context.getSystemService(
+                                    PermissionManager.class);
+                            // Check if the app requests any privileged permissions because that
+                            // violates the privapp-permissions allowlist check during boot.
+                            if (permissionManager != null) {
+                                for (int i = 0; i < usesPermissions.size(); i++) {
+                                    final String permissionName = usesPermissions.get(i).getName();
+                                    final PermissionInfo permissionInfo =
+                                            permissionManager.getPermissionInfo(permissionName, 0);
+                                    if (permissionInfo != null
+                                            && (permissionInfo.getProtectionFlags()
+                                            & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) {
+                                        usesPrivilegedPermissions.add(permissionName);
+                                    }
+                                }
+                            }
+
+                            if (!usesPrivilegedPermissions.isEmpty()) {
+                                throw new ReconcileFailure(INSTALL_FAILED_INVALID_APK,
+                                        "Non-system package: " + installPackageName
+                                                + " shares signature and sharedUserId with"
+                                                + " a privileged package but requests"
+                                                + " privileged permissions that are not"
+                                                + " allowed: " + Arrays.toString(
+                                                        usesPrivilegedPermissions.toArray()));
+                            }
+                        }
                         // Attempt to merge the existing lineage for the shared SigningDetails with
                         // the lineage of the new package; if the shared SigningDetails are not
                         // returned this indicates the new package added new signers to the lineage
@@ -189,7 +226,7 @@
                             for (AndroidPackage androidPackage : sharedUserSetting.getPackages()) {
                                 if (androidPackage.getPackageName() != null
                                         && !androidPackage.getPackageName().equals(
-                                        parsedPackage.getPackageName())) {
+                                                installPackageName)) {
                                     mergedDetails = mergedDetails.mergeLineageWith(
                                             androidPackage.getSigningDetails(),
                                             MERGE_RESTRICTED_CAPABILITY);
@@ -219,7 +256,7 @@
                     if (sharedUserSetting != null) {
                         if (sharedUserSetting.signaturesChanged != null
                                 && !PackageManagerServiceUtils.canJoinSharedUserId(
-                                parsedPackage.getPackageName(), parsedPackage.getSigningDetails(),
+                                installPackageName, parsedPackage.getSigningDetails(),
                                 sharedUserSetting,
                                 PackageManagerServiceUtils.SHARED_USER_ID_JOIN_TYPE_SYSTEM)) {
                             if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
@@ -240,7 +277,7 @@
                                 // whichever package happened to be scanned later.
                                 throw new IllegalStateException(
                                         "Signature mismatch on system package "
-                                                + parsedPackage.getPackageName()
+                                                + installPackageName
                                                 + " for shared user "
                                                 + sharedUserSetting);
                             }
@@ -252,7 +289,7 @@
                         sharedUserSetting.signaturesChanged = Boolean.TRUE;
                     }
                     // File a report about this.
-                    String msg = "System package " + parsedPackage.getPackageName()
+                    String msg = "System package " + installPackageName
                             + " signature changed; retaining data.";
                     PackageManagerService.reportSettingsProblem(Log.WARN, msg);
                 } catch (IllegalArgumentException e) {
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index e5e32f0..4d2b119 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -206,8 +206,6 @@
     static {
         SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
         SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
-        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE);
-        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND);
     }
 
     private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index c5f939a..297ad73 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1332,9 +1332,7 @@
                     // Bg location is one-off runtime modifier permission and has no app op
                     if (sPlatformPermissions.containsKey(permission)
                             && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)
-                            && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)
-                            && !Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND
-                            .equals(permission)) {
+                            && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)) {
                         Slog.wtf(LOG_TAG, "Platform runtime permission " + permission
                                 + " with no app op defined!");
                     }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index cc2c9ad..3492b26 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -23,6 +23,7 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAULT;
+import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED;
 import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED;
 import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
@@ -3655,6 +3656,26 @@
 
         for (String permission : pkg.getRequestedPermissions()) {
             Integer permissionState = permissionStates.get(permission);
+
+            if (Objects.equals(permission, Manifest.permission.USE_FULL_SCREEN_INTENT)
+                    && permissionState == null) {
+                final PackageStateInternal ps;
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    ps = mPackageManagerInt.getPackageStateInternal(pkg.getPackageName());
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                final String[] useFullScreenIntentPackageNames =
+                        mContext.getResources().getStringArray(
+                                com.android.internal.R.array.config_useFullScreenIntentPackages);
+                final boolean canUseFullScreenIntent = (ps != null && ps.isSystem())
+                        || ArrayUtils.contains(useFullScreenIntentPackageNames,
+                                pkg.getPackageName());
+                permissionState = canUseFullScreenIntent ? PERMISSION_STATE_GRANTED
+                        : PERMISSION_STATE_DENIED;
+            }
+
             if (permissionState == null || permissionState == PERMISSION_STATE_DEFAULT) {
                 continue;
             }
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 401eac6..7a5664f 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -316,6 +316,7 @@
     private int resolveDatasourceOp(int code, int uid, @NonNull String packageName,
             @Nullable String attributionTag) {
         code = resolveRecordAudioOp(code, uid);
+        code = resolveSandboxedServiceOp(code, uid);
         if (attributionTag == null) {
             return code;
         }
@@ -439,6 +440,28 @@
         return code;
     }
 
+    private int resolveSandboxedServiceOp(int code, int uid) {
+        if (!Process.isIsolated(uid) // simple check which fails-fast for the common case
+                 || !(code == AppOpsManager.OP_RECORD_AUDIO || code == AppOpsManager.OP_CAMERA)) {
+            return code;
+        }
+        final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity =
+                mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity();
+        if (hotwordDetectionServiceIdentity != null
+                && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) {
+            // Upgrade the op such that no indicators is shown for camera or audio service. This
+            // will bypass the permission checking for the original OP_RECORD_AUDIO and OP_CAMERA.
+            switch (code) {
+                case AppOpsManager.OP_RECORD_AUDIO:
+                    return AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
+                case AppOpsManager.OP_CAMERA:
+                    return AppOpsManager.OP_CAMERA_SANDBOXED;
+            }
+        }
+        return code;
+    }
+
+
     private int resolveUid(int code, int uid) {
         // The HotwordDetectionService is an isolated service, which ordinarily cannot hold
         // permissions. So we allow it to assume the owning package identity for certain
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8165958..fc6b4e9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -222,6 +222,7 @@
 import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.vr.VrManagerInternal;
+import com.android.server.wallpaper.WallpaperManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.DisplayPolicy;
 import com.android.server.wm.DisplayRotation;
@@ -412,6 +413,9 @@
     SensorPrivacyManager mSensorPrivacyManager;
     DisplayManager mDisplayManager;
     DisplayManagerInternal mDisplayManagerInternal;
+
+    private WallpaperManagerInternal mWallpaperManagerInternal;
+
     boolean mPreloadedRecentApps;
     final Object mServiceAcquireLock = new Object();
     Vibrator mVibrator; // Vibrator for giving feedback of orientation changes
@@ -5016,11 +5020,34 @@
         return bootCompleted ? mKeyguardDrawnTimeout : 5000;
     }
 
+    @Nullable
+    private WallpaperManagerInternal getWallpaperManagerInternal() {
+        if (mWallpaperManagerInternal == null) {
+            mWallpaperManagerInternal = LocalServices.getService(WallpaperManagerInternal.class);
+        }
+        return mWallpaperManagerInternal;
+    }
+
+    private void reportScreenTurningOnToWallpaper(int displayId) {
+        WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
+        if (wallpaperManagerInternal != null) {
+            wallpaperManagerInternal.onScreenTurningOn(displayId);
+        }
+    }
+
+    private void reportScreenTurnedOnToWallpaper(int displayId) {
+        WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
+        if (wallpaperManagerInternal != null) {
+            wallpaperManagerInternal.onScreenTurnedOn(displayId);
+        }
+    }
+
     // Called on the DisplayManager's DisplayPowerController thread.
     @Override
     public void screenTurningOn(int displayId, final ScreenOnListener screenOnListener) {
         if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turning on...");
 
+        reportScreenTurningOnToWallpaper(displayId);
         if (displayId == DEFAULT_DISPLAY) {
             Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn",
                     0 /* cookie */);
@@ -5061,6 +5088,8 @@
     public void screenTurnedOn(int displayId) {
         if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turned on...");
 
+        reportScreenTurnedOnToWallpaper(displayId);
+
         if (displayId != DEFAULT_DISPLAY) {
             return;
         }
diff --git a/services/core/java/com/android/server/power/ShutdownCheckPoints.java b/services/core/java/com/android/server/power/ShutdownCheckPoints.java
index 32f1bcf..546dc81 100644
--- a/services/core/java/com/android/server/power/ShutdownCheckPoints.java
+++ b/services/core/java/com/android/server/power/ShutdownCheckPoints.java
@@ -295,11 +295,18 @@
         @Nullable
         String getProcessName() {
             try {
-                List<ActivityManager.RunningAppProcessInfo> runningProcesses =
-                        mActivityManager.getRunningAppProcesses();
-                for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
-                    if (processInfo.pid == mCallerProcessId) {
-                        return processInfo.processName;
+                List<ActivityManager.RunningAppProcessInfo> runningProcesses = null;
+                if (mActivityManager != null) {
+                    runningProcesses = mActivityManager.getRunningAppProcesses();
+                } else {
+                    Slog.v(TAG, "No ActivityManager available to find process name with pid="
+                            + mCallerProcessId);
+                }
+                if (runningProcesses != null) {
+                    for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
+                        if (processInfo.pid == mCallerProcessId) {
+                            return processInfo.processName;
+                        }
                     }
                 }
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index c2d4ac6..b1430e7 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -703,17 +703,20 @@
             // vibrate before shutting down
             Vibrator vibrator = new SystemVibrator(context);
             try {
-                vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
+                if (vibrator.hasVibrator()) {
+                    vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
+                    // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
+                    try {
+                        Thread.sleep(SHUTDOWN_VIBRATE_MS);
+                    } catch (InterruptedException unused) {
+                        // this is not critical and does not require logging
+                    }
+                }
             } catch (Exception e) {
                 // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
                 Log.w(TAG, "Failed to vibrate during shutdown.", e);
             }
 
-            // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
-            try {
-                Thread.sleep(SHUTDOWN_VIBRATE_MS);
-            } catch (InterruptedException unused) {
-            }
         }
         // Shutdown power
         Log.i(TAG, "Performing low-level shutdown...");
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 7beb1ed..e437be8 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -105,36 +105,46 @@
     @Override
     public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
             @FailureReasons int failureReason, int mitigationCount) {
-        // For native crashes, we will roll back any available rollbacks
+        boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class)
+                .getAvailableRollbacks().isEmpty();
+        int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+
         if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
-                && !mContext.getSystemService(RollbackManager.class)
-                .getAvailableRollbacks().isEmpty()) {
-            return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
-        }
-        if (getAvailableRollback(failedPackage) == null) {
-            // Don't handle the notification, no rollbacks available for the package
-            return PackageHealthObserverImpact.USER_IMPACT_NONE;
-        } else {
+                && anyRollbackAvailable) {
+            // For native crashes, we will directly roll back any available rollbacks
+            // Note: For non-native crashes the rollback-all step has higher impact
+            impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+        } else if (mitigationCount == 1 && getAvailableRollback(failedPackage) != null) {
             // Rollback is available, we may get a callback into #execute
-            return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+            impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+        } else if (mitigationCount > 1 && anyRollbackAvailable) {
+            // If any rollbacks are available, we will commit them
+            impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
         }
+
+        return impact;
     }
 
     @Override
     public boolean execute(@Nullable VersionedPackage failedPackage,
             @FailureReasons int rollbackReason, int mitigationCount) {
         if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
-            mHandler.post(() -> rollbackAll());
+            mHandler.post(() -> rollbackAll(rollbackReason));
             return true;
         }
 
-        RollbackInfo rollback = getAvailableRollback(failedPackage);
-        if (rollback == null) {
-            Slog.w(TAG, "Expected rollback but no valid rollback found for " + failedPackage);
-            return false;
+        if (mitigationCount == 1) {
+            RollbackInfo rollback = getAvailableRollback(failedPackage);
+            if (rollback == null) {
+                Slog.w(TAG, "Expected rollback but no valid rollback found for " + failedPackage);
+                return false;
+            }
+            mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+        } else if (mitigationCount > 1) {
+            mHandler.post(() -> rollbackAll(rollbackReason));
         }
-        mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
-        // Assume rollback executed successfully
+
+        // Assume rollbacks executed successfully
         return true;
     }
 
@@ -468,7 +478,7 @@
     }
 
     @WorkerThread
-    private void rollbackAll() {
+    private void rollbackAll(@FailureReasons int rollbackReason) {
         assertInWorkerThread();
         RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
         List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
@@ -487,7 +497,7 @@
 
         for (RollbackInfo rollback : rollbacks) {
             VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
-            rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
+            rollbackPackage(rollback, sample, rollbackReason);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
index 584fbdd..3699557 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
@@ -27,4 +27,10 @@
      * Notifies the display is ready for adding wallpaper on it.
      */
     public abstract void onDisplayReady(int displayId);
+
+    /** Notifies when the screen finished turning on and is visible to the user. */
+    public abstract void onScreenTurnedOn(int displayId);
+
+    /** Notifies when the screen starts turning on and is not yet visible to the user. */
+    public abstract void onScreenTurningOn(int displayId);
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4a03628..e178669 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1613,6 +1613,15 @@
         public void onDisplayReady(int displayId) {
             onDisplayReadyInternal(displayId);
         }
+
+        @Override
+        public void onScreenTurnedOn(int displayId) {
+            notifyScreenTurnedOn(displayId);
+        }
+        @Override
+        public void onScreenTurningOn(int displayId) {
+            notifyScreenTurningOn(displayId);
+        }
     }
 
     void initialize() {
@@ -2442,6 +2451,54 @@
         }
     }
 
+    /**
+     * Propagates screen turned on event to wallpaper engine.
+     */
+    @Override
+    public void notifyScreenTurnedOn(int displayId) {
+        synchronized (mLock) {
+            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
+            if (data != null
+                    && data.connection != null
+                    && data.connection.containsDisplay(displayId)) {
+                final IWallpaperEngine engine = data.connection
+                        .getDisplayConnectorOrCreate(displayId).mEngine;
+                if (engine != null) {
+                    try {
+                        engine.onScreenTurnedOn();
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+
+
+    /**
+     * Propagate screen turning on event to wallpaper engine.
+     */
+    @Override
+    public void notifyScreenTurningOn(int displayId) {
+        synchronized (mLock) {
+            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
+            if (data != null
+                    && data.connection != null
+                    && data.connection.containsDisplay(displayId)) {
+                final IWallpaperEngine engine = data.connection
+                        .getDisplayConnectorOrCreate(displayId).mEngine;
+                if (engine != null) {
+                    try {
+                        engine.onScreenTurningOn();
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
     @Override
     public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
         checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index 83804f7..32f7b96 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -466,8 +466,7 @@
     }
 
     boolean isAnimatingByRecents(@NonNull Task task) {
-        return task.isAnimatingByRecents()
-                || mService.mAtmService.getTransitionController().inRecentsTransition(task);
+        return task.isAnimatingByRecents();
     }
 
     void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index df360b8..f300113 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -25,6 +25,8 @@
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 
+import static com.android.internal.util.DumpUtils.dumpSparseArray;
+import static com.android.internal.util.DumpUtils.dumpSparseArrayValues;
 import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY;
 import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER;
 import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER_H;
@@ -44,8 +46,6 @@
 import static com.android.server.accessibility.AccessibilityTraceProto.WINDOW_MANAGER_SERVICE;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.dumpSparseArray;
-import static com.android.server.wm.WindowManagerService.dumpSparseArrayValues;
 import static com.android.server.wm.WindowTracing.WINSCOPE_EXT;
 
 import android.accessibilityservice.AccessibilityTrace;
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index afe1640..70f2007 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -16,8 +16,9 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.WindowManagerService.ValueDumper;
-import static com.android.server.wm.WindowManagerService.dumpSparseArray;
+import static com.android.internal.util.DumpUtils.KeyDumper;
+import static com.android.internal.util.DumpUtils.ValueDumper;
+import static com.android.internal.util.DumpUtils.dumpSparseArray;
 import static com.android.server.wm.utils.RegionUtils.forEachRect;
 
 import android.annotation.NonNull;
@@ -41,7 +42,6 @@
 import android.window.WindowInfosListener;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.server.wm.WindowManagerService.KeyDumper;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index ff1c28a..7718dd8 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -78,8 +78,6 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.Bundle;
@@ -1147,8 +1145,7 @@
         if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                     "Creating Pending Multiwindow Fullscreen Request: %s", transition);
-            mService.mWindowManager.mSyncEngine.queueSyncSet(
-                    () -> r.mTransitionController.moveToCollecting(transition),
+            r.mTransitionController.queueCollecting(transition,
                     () -> {
                         executeFullscreenRequestTransition(fullscreenRequest, callback, r,
                                 transition, true /* queued */);
@@ -1645,18 +1642,15 @@
                 launchedFromHome = root.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
             }
 
-            // If the activity is one of the main entry points for the application, then we should
+            // If the activity was launched directly from the home screen, then we should
             // refrain from finishing the activity and instead move it to the back to keep it in
             // memory. The requirements for this are:
             //   1. The activity is the last running activity in the task.
             //   2. The current activity is the base activity for the task.
-            //   3. a. If the activity was launched by the home process, we trust that its intent
-            //         was resolved, so we check if the it is a main intent for the application.
-            //      b. Otherwise, we query Package Manager to verify whether the activity is a
-            //         launcher activity for the application.
+            //   3. The activity was launched by the home process, and is one of the main entry
+            //      points for the application.
             if (baseActivityIntent != null && isLastRunningActivity
-                    && ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent))
-                        || isLauncherActivity(baseActivityIntent.getComponent()))) {
+                    && launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent)) {
                 moveActivityTaskToBack(token, true /* nonRoot */);
                 return;
             }
@@ -1668,31 +1662,6 @@
         }
     }
 
-    /**
-     * Queries PackageManager to see if the given activity is one of the main entry point for the
-     * application. This should not be called with the WM lock held.
-     */
-    @SuppressWarnings("unchecked")
-    private boolean isLauncherActivity(@NonNull ComponentName activity) {
-        final Intent queryIntent = new Intent(Intent.ACTION_MAIN);
-        queryIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-        queryIntent.setPackage(activity.getPackageName());
-        try {
-            final ParceledListSlice<ResolveInfo> resolved =
-                    mService.getPackageManager().queryIntentActivities(
-                            queryIntent, null, 0, mContext.getUserId());
-            if (resolved == null) return false;
-            for (final ResolveInfo ri : resolved.getList()) {
-                if (ri.getComponentInfo().getComponentName().equals(activity)) {
-                    return true;
-                }
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to query intent activities", e);
-        }
-        return false;
-    }
-
     @Override
     public void enableTaskLocaleOverride(IBinder token) {
         if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fb1f899..0b98495 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3544,8 +3544,7 @@
                 // the best capture timing (e.g. IME window capture),
                 // No need additional task capture while task is controlled by RecentsAnimation.
                 if (mAtmService.mWindowManager.mTaskSnapshotController != null
-                        && !(task.isAnimatingByRecents()
-                                || mTransitionController.inRecentsTransition(task))) {
+                        && !task.isAnimatingByRecents()) {
                     final ArraySet<Task> tasks = Sets.newArraySet(task);
                     mAtmService.mWindowManager.mTaskSnapshotController.snapshotTasks(tasks);
                     mAtmService.mWindowManager.mTaskSnapshotController
@@ -9821,8 +9820,7 @@
                 scheduleStopForRestartProcess();
             };
             if (mWmService.mSyncEngine.hasActiveSync()) {
-                mWmService.mSyncEngine.queueSyncSet(
-                        () -> mTransitionController.moveToCollecting(transition), executeRestart);
+                mTransitionController.queueCollecting(transition, executeRestart);
             } else {
                 mTransitionController.moveToCollecting(transition);
                 executeRestart.run();
@@ -10535,11 +10533,6 @@
 
     @Override
     boolean isSyncFinished() {
-        if (task != null && mTransitionController.isTransientHide(task)) {
-            // The activity keeps visibleRequested but may be hidden later, so no need to wait for
-            // it to be drawn.
-            return true;
-        }
         if (!super.isSyncFinished()) return false;
         if (mDisplayContent != null && mDisplayContent.mUnknownAppVisibilityController
                 .isVisibilityUnknown(this)) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 7c1e907..bfe2986 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -577,7 +577,6 @@
             final Transition transition = controller.getCollectingTransition();
             if (transition != null) {
                 transition.setRemoteAnimationApp(r.app.getThread());
-                controller.collect(task);
                 controller.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask));
             }
             task.moveToFront("startExistingRecents");
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 38f13ec..c5e75fa 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2548,6 +2548,7 @@
         mAvoidMoveToFront = false;
         mFrozeTaskList = false;
         mTransientLaunch = false;
+        mPriorAboveTask = null;
         mDisplayLockAndOccluded = false;
 
         mVoiceSession = null;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 12fe6a0..e780716 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -831,7 +831,7 @@
     private final Runnable mUpdateOomAdjRunnable = new Runnable() {
         @Override
         public void run() {
-            mAmInternal.updateOomAdj();
+            mAmInternal.updateOomAdj(ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY);
         }
     };
 
@@ -2874,8 +2874,7 @@
                 final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
                         getTransitionController(), mWindowManager.mSyncEngine);
                 if (mWindowManager.mSyncEngine.hasActiveSync()) {
-                    mWindowManager.mSyncEngine.queueSyncSet(
-                            () -> getTransitionController().moveToCollecting(transition),
+                    getTransitionController().queueCollecting(transition,
                             () -> {
                                 if (!task.getWindowConfiguration().canResizeTask()) {
                                     Slog.w(TAG, "resizeTask not allowed on task=" + task);
@@ -3629,9 +3628,7 @@
                     if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) {
                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                                 "Creating Pending Pip-Enter: %s", transition);
-                        mWindowManager.mSyncEngine.queueSyncSet(
-                                () -> getTransitionController().moveToCollecting(transition),
-                                enterPipRunnable);
+                        getTransitionController().queueCollecting(transition, enterPipRunnable);
                     } else {
                         // Move to collecting immediately to "claim" the sync-engine for this
                         // transition.
@@ -3647,9 +3644,7 @@
             if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "Creating Pending Pip-Enter: %s", transition);
-                mWindowManager.mSyncEngine.queueSyncSet(
-                        () -> getTransitionController().moveToCollecting(transition),
-                        enterPipRunnable);
+                getTransitionController().queueCollecting(transition, enterPipRunnable);
             } else {
                 if (transition != null) {
                     getTransitionController().moveToCollecting(transition);
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 7b562b0..11d84ff 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -532,14 +532,7 @@
             if (newFocus != null && newFocus != mNavigatingWindow
                     && (newFocus.mActivityRecord == null
                     || (newFocus.mActivityRecord == mNavigatingWindow.mActivityRecord))) {
-                EventLogTags.writeWmBackNaviCanceled("focusWindowChanged");
-                if (isMonitorForRemote()) {
-                    mObserver.sendResult(null /* result */);
-                }
-                if (isMonitorAnimationOrTransition()) {
-                    // transition won't happen, cancel internal status
-                    clearBackAnimations();
-                }
+                cancelBackNavigating("focusWindowChanged");
             }
         }
 
@@ -553,19 +546,12 @@
             }
             final ArrayList<WindowContainer> all = new ArrayList<>(opening);
             all.addAll(closing);
-            for (WindowContainer app : all) {
-                if (app.hasChild(mNavigatingWindow)) {
-                    EventLogTags.writeWmBackNaviCanceled("transitionHappens");
-                    if (isMonitorForRemote()) {
-                        mObserver.sendResult(null /* result */);
-                    }
-                    if (isMonitorAnimationOrTransition()) {
-                        clearBackAnimations();
-                    }
+            for (int i = all.size() - 1; i >= 0; --i) {
+                if (all.get(i).hasChild(mNavigatingWindow)) {
+                    cancelBackNavigating("transitionHappens");
                     break;
                 }
             }
-
         }
 
         private boolean atSameDisplay(WindowState newFocus) {
@@ -575,6 +561,17 @@
             final int navigatingDisplayId = mNavigatingWindow.getDisplayId();
             return newFocus == null || newFocus.getDisplayId() == navigatingDisplayId;
         }
+
+        private void cancelBackNavigating(String reason) {
+            EventLogTags.writeWmBackNaviCanceled(reason);
+            if (isMonitorForRemote()) {
+                mObserver.sendResult(null /* result */);
+            }
+            if (isMonitorAnimationOrTransition()) {
+                clearBackAnimations();
+            }
+            cancelPendingAnimation();
+        }
     }
 
     // For shell transition
@@ -648,31 +645,28 @@
         if (finishedTransition == mWaitTransitionFinish) {
             clearBackAnimations();
         }
+
         if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) {
             return false;
         }
-
         ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
                 "Handling the deferred animation after transition finished");
 
-        // Show the target surface and its parents to prevent it or its parents hidden when
-        // the transition finished.
-        // The target could be affected by transition when :
-        // Open transition -> the open target in back navigation
-        // Close transition -> the close target in back navigation.
+        // Find the participated container collected by transition when :
+        // Open transition -> the open target in back navigation, the close target in transition.
+        // Close transition -> the close target in back navigation, the open target in transition.
         boolean hasTarget = false;
-        final SurfaceControl.Transaction t =
-                mPendingAnimationBuilder.mCloseTarget.getPendingTransaction();
-        for (int i = 0; i < targets.size(); i++) {
-            final WindowContainer wc = targets.get(i).mContainer;
-            if (wc.asActivityRecord() == null && wc.asTask() == null) {
-                continue;
-            } else if (!mPendingAnimationBuilder.containTarget(wc)) {
+        for (int i = 0; i < finishedTransition.mParticipants.size(); i++) {
+            final WindowContainer wc = finishedTransition.mParticipants.valueAt(i);
+            if (wc.asActivityRecord() == null && wc.asTask() == null
+                    && wc.asTaskFragment() == null) {
                 continue;
             }
 
-            hasTarget = true;
-            t.show(wc.getSurfaceControl());
+            if (mPendingAnimationBuilder.containTarget(wc)) {
+                hasTarget = true;
+                break;
+            }
         }
 
         if (!hasTarget) {
@@ -680,20 +674,33 @@
             Slog.w(TAG, "Finished transition didn't include the targets"
                     + " open: " + mPendingAnimationBuilder.mOpenTarget
                     + " close: " + mPendingAnimationBuilder.mCloseTarget);
-            try {
-                mPendingAnimationBuilder.mBackAnimationAdapter.getRunner().onAnimationCancelled();
-            } catch (RemoteException e) {
-                throw new RuntimeException(e);
-            }
-            mPendingAnimationBuilder = null;
+            cancelPendingAnimation();
             return false;
         }
 
+        // Ensure the final animation targets which hidden by transition could be visible.
+        for (int i = 0; i < targets.size(); i++) {
+            final WindowContainer wc = targets.get(i).mContainer;
+            wc.prepareSurfaces();
+        }
+
         scheduleAnimation(mPendingAnimationBuilder);
         mPendingAnimationBuilder = null;
         return true;
     }
 
+    private void cancelPendingAnimation() {
+        if (mPendingAnimationBuilder == null) {
+            return;
+        }
+        try {
+            mPendingAnimationBuilder.mBackAnimationAdapter.getRunner().onAnimationCancelled();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote animation gone", e);
+        }
+        mPendingAnimationBuilder = null;
+    }
+
     /**
      * Create and handling animations status for an open/close animation targets.
      */
@@ -1076,7 +1083,7 @@
 
             boolean containTarget(@NonNull WindowContainer wc) {
                 return wc == mOpenTarget || wc == mCloseTarget
-                        || wc.hasChild(mOpenTarget) || wc.hasChild(mCloseTarget);
+                        || mOpenTarget.hasChild(wc) || mCloseTarget.hasChild(wc);
             }
 
             /**
@@ -1151,6 +1158,11 @@
     private static void setLaunchBehind(@NonNull ActivityRecord activity) {
         if (!activity.isVisibleRequested()) {
             activity.setVisibility(true);
+            // The transition could commit the visibility and in the finishing state, that could
+            // skip commitVisibility call in setVisibility cause the activity won't visible here.
+            // Call it again to make sure the activity could be visible while handling the pending
+            // animation.
+            activity.commitVisibility(true, true);
         }
         activity.mLaunchTaskBehind = true;
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 57fca3ae..2378469 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -146,8 +146,6 @@
 
     @VisibleForTesting
     boolean mIsAddingTaskToTargets;
-    @VisibleForTesting
-    boolean mShouldAttachNavBarToAppDuringTransition;
     private boolean mNavigationBarAttachedToApp;
     private ActivityRecord mNavBarAttachedApp;
 
@@ -379,8 +377,6 @@
         mDisplayId = displayId;
         mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
         mDisplayContent = service.mRoot.getDisplayContent(displayId);
-        mShouldAttachNavBarToAppDuringTransition =
-                mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition();
     }
 
     /**
@@ -577,7 +573,7 @@
     }
 
     private void attachNavigationBarToApp() {
-        if (!mShouldAttachNavBarToAppDuringTransition
+        if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
                 // Skip the case where the nav bar is controlled by fade rotation.
                 || mDisplayContent.getAsyncRotationController() != null) {
             return;
@@ -652,7 +648,7 @@
     }
 
     void animateNavigationBarForAppLaunch(long duration) {
-        if (!mShouldAttachNavBarToAppDuringTransition
+        if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
                 // Skip the case where the nav bar is controlled by fade rotation.
                 || mDisplayContent.getAsyncRotationController() != null
                 || mNavigationBarAttachedToApp
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 07daa4b..ad93454 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2335,9 +2335,7 @@
                     transition.playNow();
                 };
                 if (display.mTransitionController.isCollecting()) {
-                    mWmService.mSyncEngine.queueSyncSet(
-                            () -> display.mTransitionController.moveToCollecting(transition),
-                            sendSleepTransition);
+                    display.mTransitionController.queueCollecting(transition, sendSleepTransition);
                 } else {
                     display.mTransitionController.moveToCollecting(transition);
                     sendSleepTransition.run();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 73f4b5b..89f9753 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3006,7 +3006,8 @@
 
     /** Checking if self or its child tasks are animated by recents animation. */
     boolean isAnimatingByRecents() {
-        return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS);
+        return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS)
+                || mTransitionController.isTransientHide(this);
     }
 
     WindowState getTopVisibleAppMainWindow() {
@@ -5634,8 +5635,7 @@
             if (mWmService.mSyncEngine.hasActiveSync()) {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "Creating Pending Move-to-back: %s", transition);
-                mWmService.mSyncEngine.queueSyncSet(
-                        () -> mTransitionController.moveToCollecting(transition),
+                mTransitionController.queueCollecting(transition,
                         () -> {
                             // Need to check again since this happens later and the system might
                             // be in a different state.
@@ -5683,6 +5683,7 @@
             // Usually resuming a top activity triggers the next app transition, but nothing's got
             // resumed in this case, so we need to execute it explicitly.
             mDisplayContent.executeAppTransition();
+            mDisplayContent.setFocusedApp(topActivity);
         } else {
             mRootWindowContainer.resumeFocusedTasksTopActivities();
         }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e209ef9..652c297 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -109,7 +109,7 @@
  */
 class Transition implements BLASTSyncEngine.TransactionReadyListener {
     private static final String TAG = "Transition";
-    private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition";
+    private static final String TRACE_NAME_PLAY_TRANSITION = "playing";
 
     /** The default package for resources */
     private static final String DEFAULT_PACKAGE = "android";
@@ -287,7 +287,7 @@
 
         if (restoreBelow != null) {
             final Task transientRootTask = activity.getRootTask();
-            // Collect all visible activities which can be occluded by the transient activity to
+            // Collect all visible tasks which can be occluded by the transient activity to
             // make sure they are in the participants so their visibilities can be updated when
             // finishing transition.
             ((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> {
@@ -297,11 +297,7 @@
                         mTransientHideTasks.add(t);
                     }
                     if (t.isLeafTask()) {
-                        t.forAllActivities(r -> {
-                            if (r.isVisibleRequested()) {
-                                collect(r);
-                            }
-                        });
+                        collect(t);
                     }
                 }
                 return t == restoreBelow;
@@ -511,8 +507,10 @@
         if (mParticipants.contains(wc)) return;
         // Wallpaper is like in a static drawn state unless display may have changes, so exclude
         // the case to reduce transition latency waiting for the unchanged wallpaper to redraw.
-        final boolean needSyncDraw = !isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent);
-        if (needSyncDraw) {
+        final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent))
+                // Transient-hide may be hidden later, so no need to request redraw.
+                && !isInTransientHide(wc);
+        if (needSync) {
             mSyncEngine.addToSyncSet(mSyncId, wc);
         }
         ChangeInfo info = mChanges.get(wc);
@@ -881,8 +879,7 @@
      */
     void finishTransition() {
         if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) {
-            Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
-                    System.identityHashCode(this));
+            asyncTraceEnd(System.identityHashCode(this));
         }
         mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos();
         mController.mLoggerHandler.post(mLogger::logOnFinish);
@@ -903,6 +900,18 @@
         mController.mFinishingTransition = this;
 
         if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) {
+            // Record all the now-hiding activities so that they are committed after
+            // recalculating visibilities. We just use mParticipants because we can and it will
+            // ensure proper reporting of `isInFinishTransition`.
+            for (int i = 0; i < mTransientHideTasks.size(); ++i) {
+                mTransientHideTasks.get(i).forAllActivities(r -> {
+                    // Only check leaf-tasks that were collected
+                    if (!mParticipants.contains(r.getTask())) return;
+                    // Only concern ourselves with anything that can become invisible
+                    if (!r.isVisible()) return;
+                    mParticipants.add(r);
+                });
+            }
             // The transient hide tasks could be occluded now, e.g. returning to home. So trigger
             // the update to make the activities in the tasks invisible-requested, then the next
             // step can continue to commit the visibility.
@@ -918,6 +927,8 @@
             final WindowContainer<?> participant = mParticipants.valueAt(i);
             final ActivityRecord ar = participant.asActivityRecord();
             if (ar != null) {
+                final Task task = ar.getTask();
+                if (task == null) continue;
                 boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar);
                 // We need both the expected visibility AND current requested-visibility to be
                 // false. If it is expected-visible but not currently visible, it means that
@@ -936,9 +947,7 @@
                     if (commitVisibility) {
                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                                 "  Commit activity becoming invisible: %s", ar);
-                        final Task task = ar.getTask();
-                        if (task != null && !task.isVisibleRequested()
-                                && mTransientLaunches != null) {
+                        if (mTransientLaunches != null && !task.isVisibleRequested()) {
                             // If transition is transient, then snapshots are taken at end of
                             // transition.
                             mController.mSnapshotController.mTaskSnapshotController
@@ -952,7 +961,10 @@
                         enterAutoPip = true;
                     }
                 }
-                if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) {
+                final ChangeInfo changeInfo = mChanges.get(ar);
+                // Due to transient-hide, there may be some activities here which weren't in the
+                // transition.
+                if (changeInfo != null && changeInfo.mVisible != visibleAtTransitionEnd) {
                     // Legacy dispatch relies on this (for now).
                     ar.mEnteringAnimation = visibleAtTransitionEnd;
                 } else if (mTransientLaunches != null && mTransientLaunches.containsKey(ar)
@@ -963,8 +975,9 @@
 
                     // Since transient launches don't automatically take focus, make sure we
                     // synchronize focus since we committed to the launch.
-                    if (ar.isTopRunningActivity()) {
-                        ar.moveFocusableActivityToTop("transitionFinished");
+                    if (!task.isFocused() && ar.isTopRunningActivity()) {
+                        mController.mAtm.setLastResumedActivityUncheckLocked(ar,
+                                "transitionFinished");
                     }
                 }
                 continue;
@@ -1332,8 +1345,7 @@
                 mController.getTransitionPlayer().onTransitionReady(
                         mToken, info, transaction, mFinishTransaction);
                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
-                    Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
-                            System.identityHashCode(this));
+                    asyncTraceBegin(TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this));
                 }
             } catch (RemoteException e) {
                 // If there's an exception when trying to send the mergedTransaction to the
@@ -1608,7 +1620,7 @@
         sb.append(Integer.toHexString(System.identityHashCode(this)));
         sb.append(" id=" + mSyncId);
         sb.append(" type=" + transitTypeToString(mType));
-        sb.append(" flags=" + mFlags);
+        sb.append(" flags=0x" + Integer.toHexString(mFlags));
         sb.append('}');
         return sb.toString();
     }
@@ -2320,6 +2332,14 @@
         return isCollecting() && mSyncId >= 0;
     }
 
+    static void asyncTraceBegin(@NonNull String name, int cookie) {
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, name, cookie);
+    }
+
+    static void asyncTraceEnd(int cookie) {
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie);
+    }
+
     @VisibleForTesting
     static class ChangeInfo {
         private static final int FLAG_NONE = 0;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 8d56607..4c1c2ff 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -37,7 +37,6 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.TimeUtils;
@@ -334,28 +333,6 @@
         return inCollectingTransition(wc) || inPlayingTransition(wc);
     }
 
-    boolean inRecentsTransition(@NonNull WindowContainer wc) {
-        for (WindowContainer p = wc; p != null; p = p.getParent()) {
-            // TODO(b/221417431): replace this with deterministic snapshots
-            if (mCollectingTransition == null) break;
-            if ((mCollectingTransition.getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0
-                    && mCollectingTransition.mParticipants.contains(wc)) {
-                return true;
-            }
-        }
-
-        for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
-            for (WindowContainer p = wc; p != null; p = p.getParent()) {
-                // TODO(b/221417431): replace this with deterministic snapshots
-                if ((mPlayingTransitions.get(i).getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0
-                        && mPlayingTransitions.get(i).mParticipants.contains(p)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     /** @return {@code true} if wc is in a participant subtree */
     boolean isTransitionOnDisplay(@NonNull DisplayContent dc) {
         if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) {
@@ -466,26 +443,36 @@
         return type == TRANSIT_OPEN || type == TRANSIT_CLOSE;
     }
 
-    /** Whether the display change should run with blast sync. */
-    private static boolean shouldSync(@NonNull TransitionRequestInfo.DisplayChange displayChange) {
-        if ((displayChange.getStartRotation() + displayChange.getEndRotation()) % 2 == 0) {
+    /** Sets the sync method for the display change. */
+    private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
+            @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) {
+        final int startRotation = displayChange.getStartRotation();
+        final int endRotation = displayChange.getEndRotation();
+        if (startRotation != endRotation && (startRotation + endRotation) % 2 == 0) {
             // 180 degrees rotation change may not change screen size. So the clients may draw
             // some frames before and after the display projection transaction is applied by the
             // remote player. That may cause some buffers to show in different rotation. So use
             // sync method to pause clients drawing until the projection transaction is applied.
-            return true;
+            mAtm.mWindowManager.mSyncEngine.setSyncMethod(displayTransition.getSyncId(),
+                    BLASTSyncEngine.METHOD_BLAST);
         }
         final Rect startBounds = displayChange.getStartAbsBounds();
         final Rect endBounds = displayChange.getEndAbsBounds();
-        if (startBounds == null || endBounds == null) return false;
+        if (startBounds == null || endBounds == null) return;
         final int startWidth = startBounds.width();
         final int startHeight = startBounds.height();
         final int endWidth = endBounds.width();
         final int endHeight = endBounds.height();
         // This is changing screen resolution. Because the screen decor layers are excluded from
         // screenshot, their draw transactions need to run with the start transaction.
-        return (endWidth > startWidth) == (endHeight > startHeight)
-                && (endWidth != startWidth || endHeight != startHeight);
+        if ((endWidth > startWidth) == (endHeight > startHeight)
+                && (endWidth != startWidth || endHeight != startHeight)) {
+            displayContent.forAllWindows(w -> {
+                if (w.mToken.mRoundedCornerOverlay && w.mHasSurface) {
+                    w.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
+                }
+            }, true /* traverseTopToBottom */);
+        }
     }
 
     /**
@@ -517,9 +504,9 @@
         } else {
             newTransition = requestStartTransition(createTransition(type, flags),
                     trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
-            if (newTransition != null && displayChange != null && shouldSync(displayChange)) {
-                mAtm.mWindowManager.mSyncEngine.setSyncMethod(newTransition.getSyncId(),
-                        BLASTSyncEngine.METHOD_BLAST);
+            if (newTransition != null && displayChange != null && trigger != null
+                    && trigger.asDisplayContent() != null) {
+                setDisplaySyncMethod(displayChange, newTransition, trigger.asDisplayContent());
             }
         }
         if (trigger != null) {
@@ -773,12 +760,12 @@
             // happening in app), so pause task snapshot persisting to not increase the load.
             mAtm.mWindowManager.mSnapshotController.setPause(true);
             mAnimatingState = true;
-            Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "transitAnim", 0);
+            Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */);
         } else if (!animatingState && mAnimatingState) {
             t.setEarlyWakeupEnd();
             mAtm.mWindowManager.mSnapshotController.setPause(false);
             mAnimatingState = false;
-            Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "transitAnim", 0);
+            Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */);
         }
     }
 
@@ -897,6 +884,15 @@
         proto.end(token);
     }
 
+    void queueCollecting(Transition transit, Runnable onCollectStart) {
+        mAtm.mWindowManager.mSyncEngine.queueSyncSet(
+                // Make sure to collect immediately to prevent another transition
+                // from sneaking in before it. Note: moveToCollecting internally
+                // calls startSyncSet.
+                () -> moveToCollecting(transit),
+                onCollectStart);
+    }
+
     /**
      * This manages the animating state of processes that are running remote animations for
      * {@link #mTransitionPlayerProc}.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 82c057b..8fecf11 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -235,7 +235,6 @@
 import android.util.MergedConfiguration;
 import android.util.Pair;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
@@ -9460,53 +9459,4 @@
             return List.copyOf(notifiedApps);
         }
     }
-
-    // TODO(b/271188189): move dump stuff below to common code / add unit tests
-
-    interface ValueDumper<T> {
-        void dump(T value);
-    }
-
-    interface KeyDumper{
-        void dump(int index, int key);
-    }
-
-    static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array, String name) {
-        dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valuedumper= */ null);
-    }
-
-    static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix, SparseArray<T> array,
-            String name) {
-        dumpSparseArray(pw, prefix, array, name, (i, k) -> {}, /* valueDumper= */ null);
-    }
-
-    static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array,
-            String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) {
-        int size = array.size();
-        if (size == 0) {
-            pw.print(prefix); pw.print("No "); pw.print(name); pw.println("s");
-            return;
-        }
-        pw.print(prefix); pw.print(size); pw.print(' ');
-        pw.print(name); pw.print(size > 1 ? "s" : ""); pw.println(':');
-
-        String prefix2 = prefix + "  ";
-        for (int i = 0; i < size; i++) {
-            int key = array.keyAt(i);
-            T value = array.valueAt(i);
-            if (keyDumper != null) {
-                keyDumper.dump(i, key);
-            } else {
-                pw.print(prefix2); pw.print(i); pw.print(": "); pw.print(key); pw.print("->");
-            }
-            if (value == null) {
-                pw.print("(null)");
-            } else if (valueDumper != null) {
-                valueDumper.dump(value);
-            } else {
-                pw.print(value);
-            }
-            pw.println();
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 32d54d7..f63470f2 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -291,23 +291,21 @@
                     if (type < 0) {
                         throw new IllegalArgumentException("Can't create transition with no type");
                     }
+                    transition = new Transition(type, 0 /* flags */, mTransitionController,
+                            mService.mWindowManager.mSyncEngine);
                     // If there is already a collecting transition, queue up a new transition and
                     // return that. The actual start and apply will then be deferred until that
                     // transition starts collecting. This should almost never happen except during
                     // tests.
                     if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
                         Slog.w(TAG, "startTransition() while one is already collecting.");
-                        final Transition nextTransition = new Transition(type, 0 /* flags */,
-                                mTransitionController, mService.mWindowManager.mSyncEngine);
+                        final Transition nextTransition = transition;
                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                                 "Creating Pending Transition: %s", nextTransition);
-                        mService.mWindowManager.mSyncEngine.queueSyncSet(
-                                // Make sure to collect immediately to prevent another transition
-                                // from sneaking in before it. Note: moveToCollecting internally
-                                // calls startSyncSet.
-                                () -> mTransitionController.moveToCollecting(nextTransition),
+                        mTransitionController.queueCollecting(nextTransition,
                                 () -> {
                                     nextTransition.start();
+                                    nextTransition.mLogger.mStartWCT = wct;
                                     applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
                                     if (needsSetReady) {
                                         nextTransition.setAllReady();
@@ -315,7 +313,7 @@
                                 });
                         return nextTransition.getToken();
                     }
-                    transition = mTransitionController.createTransition(type);
+                    mTransitionController.moveToCollecting(transition);
                 }
                 if (!transition.isCollecting() && !transition.isForcePlaying()) {
                     Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably"
@@ -474,11 +472,7 @@
                     mTransitionController, mService.mWindowManager.mSyncEngine);
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                     "Creating Pending Transition for TaskFragment: %s", nextTransition);
-            mService.mWindowManager.mSyncEngine.queueSyncSet(
-                    // Make sure to collect immediately to prevent another transition
-                    // from sneaking in before it. Note: moveToCollecting internally
-                    // calls startSyncSet.
-                    () -> mTransitionController.moveToCollecting(nextTransition),
+            mTransitionController.queueCollecting(nextTransition,
                     () -> {
                         if (mTaskFragmentOrganizerController.isValidTransaction(wct)
                                 && (applyTransaction(wct, -1 /* syncId */, nextTransition, caller)
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6e3924b..e5a49c3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5647,7 +5647,7 @@
 
     @Override
     boolean isSyncFinished() {
-        if (!isVisibleRequested()) {
+        if (!isVisibleRequested() || isFullyTransparent()) {
             // Don't wait for invisible windows. However, we don't alter the state in case the
             // window becomes visible while the sync group is still active.
             return true;
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index aeb4801..208a4be 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -161,7 +161,8 @@
     @Override
     public void onProviderStatusChanged(ProviderSession.Status status,
             ComponentName componentName, ProviderSession.CredentialsSource source) {
-        Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source);
+        Slog.d(TAG, "in onStatusChanged for: " + componentName + ", with status: "
+                + status + ", and source: " + source);
 
         // Auth entry was selected, and it did not have any underlying credentials
         if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 0c3d3f4..9ec0ecd 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -26,7 +26,6 @@
 import android.os.ICancellationSignal;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.ClearCredentialStateRequest;
-import android.util.Log;
 import android.util.Slog;
 
 /**
@@ -81,7 +80,7 @@
 
     @Override
     public void onProviderResponseSuccess(@Nullable Void response) {
-        Log.i(TAG, "in onProviderResponseSuccess");
+        Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
         mProviderResponseSet = true;
         updateStatusAndInvokeCallback(Status.COMPLETE,
                 /*source=*/ CredentialsSource.REMOTE_PROVIDER);
@@ -105,7 +104,7 @@
             updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
                     /*source=*/ CredentialsSource.REMOTE_PROVIDER);
         } else {
-            Slog.i(TAG, "Component names different in onProviderServiceDied - "
+            Slog.w(TAG, "Component names different in onProviderServiceDied - "
                     + "this should not happen");
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 8b9255a..09433db 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -37,7 +37,6 @@
 import android.service.credentials.CreateEntry;
 import android.service.credentials.CredentialProviderService;
 import android.service.credentials.RemoteEntry;
-import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 
@@ -93,7 +92,8 @@
                     createRequestSession.mHybridService
             );
         }
-        Log.i(TAG, "Unable to create provider session");
+        Slog.d(TAG, "Unable to create provider session for: "
+                + providerInfo.getComponentName());
         return null;
     }
 
@@ -122,7 +122,6 @@
             return new CreateCredentialRequest(callingAppInfo, capability,
                     clientRequest.getCredentialData());
         }
-        Log.i(TAG, "Unable to create provider request - capabilities do not match");
         return null;
     }
 
@@ -146,7 +145,7 @@
     @Override
     public void onProviderResponseSuccess(
             @Nullable BeginCreateCredentialResponse response) {
-        Log.i(TAG, "in onProviderResponseSuccess");
+        Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
         onSetInitialRemoteResponse(response);
     }
 
@@ -169,7 +168,7 @@
             updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
                     /*source=*/ CredentialsSource.REMOTE_PROVIDER);
         } else {
-            Slog.i(TAG, "Component names different in onProviderServiceDied - "
+            Slog.w(TAG, "Component names different in onProviderServiceDied - "
                     + "this should not happen");
         }
     }
@@ -180,7 +179,6 @@
     }
 
     private void onSetInitialRemoteResponse(BeginCreateCredentialResponse response) {
-        Log.i(TAG, "onSetInitialRemoteResponse with save entries");
         mProviderResponse = response;
         mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
                 response.getRemoteCreateEntry());
@@ -199,14 +197,12 @@
     @Nullable
     protected CreateCredentialProviderData prepareUiData()
             throws IllegalArgumentException {
-        Log.i(TAG, "In prepareUiData");
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
-            Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
+            Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
             return null;
         }
 
         if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
-            Log.i(TAG, "In prepareUiData save entries not null");
             return mProviderResponseDataHandler.toCreateCredentialProviderData();
         }
         return null;
@@ -218,7 +214,7 @@
         switch (entryType) {
             case SAVE_ENTRY_KEY:
                 if (mProviderResponseDataHandler.getCreateEntry(entryKey) == null) {
-                    Log.i(TAG, "Unexpected save entry key");
+                    Slog.w(TAG, "Unexpected save entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
@@ -226,14 +222,14 @@
                 break;
             case REMOTE_ENTRY_KEY:
                 if (mProviderResponseDataHandler.getRemoteEntry(entryKey) == null) {
-                    Log.i(TAG, "Unexpected remote entry key");
+                    Slog.w(TAG, "Unexpected remote entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
                 onRemoteEntrySelected(providerPendingIntentResponse);
                 break;
             default:
-                Log.i(TAG, "Unsupported entry type selected");
+                Slog.w(TAG, "Unsupported entry type selected");
                 invokeCallbackOnInternalInvalidState();
         }
     }
@@ -268,7 +264,7 @@
         if (credentialResponse != null) {
             mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
         } else {
-            Log.i(TAG, "onSaveEntrySelected - no response or error found in pending "
+            Slog.w(TAG, "onSaveEntrySelected - no response or error found in pending "
                     + "intent response");
             invokeCallbackOnInternalInvalidState();
         }
@@ -284,14 +280,14 @@
     private CreateCredentialException maybeGetPendingIntentException(
             ProviderPendingIntentResponse pendingIntentResponse) {
         if (pendingIntentResponse == null) {
-            Log.i(TAG, "pendingIntentResponse is null");
+            Slog.w(TAG, "pendingIntentResponse is null");
             return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREATE_OPTIONS);
         }
         if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
             CreateCredentialException exception = PendingIntentResultHandler
                     .extractCreateCredentialException(pendingIntentResponse.getResultData());
             if (exception != null) {
-                Log.i(TAG, "Pending intent contains provider exception");
+                Slog.d(TAG, "Pending intent contains provider exception");
                 return exception;
             }
         } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
@@ -343,7 +339,7 @@
 
         public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) {
             if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) {
-                Log.i(TAG, "Remote entry being dropped as it does not meet the restriction"
+                Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction"
                         + "checks.");
                 return;
             }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 8d3d064..0c2b563 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -115,7 +115,8 @@
                     getRequestSession.mHybridService
             );
         }
-        Log.i(TAG, "Unable to create provider session");
+        Slog.d(TAG, "Unable to create provider session for: "
+                + providerInfo.getComponentName());
         return null;
     }
 
@@ -146,17 +147,15 @@
             android.credentials.GetCredentialRequest clientRequest,
             CredentialProviderInfo info
     ) {
+        Slog.d(TAG, "Filtering request options for: " + info.getComponentName());
         List<CredentialOption> filteredOptions = new ArrayList<>();
         for (CredentialOption option : clientRequest.getCredentialOptions()) {
             if (providerCapabilities.contains(option.getType())
                     && isProviderAllowed(option, info.getComponentName())
                     && checkSystemProviderRequirement(option, info.isSystemProvider())) {
-                Log.i(TAG, "In createProviderRequest - capability found : "
-                        + option.getType());
+                Slog.d(TAG, "Option of type: " + option.getType() + " meets all filtering"
+                        + "conditions");
                 filteredOptions.add(option);
-            } else {
-                Log.i(TAG, "In createProviderRequest - capability not "
-                        + "found, or provider not allowed : " + option.getType());
             }
         }
         if (!filteredOptions.isEmpty()) {
@@ -165,15 +164,14 @@
                     .setCredentialOptions(
                             filteredOptions).build();
         }
-        Log.i(TAG, "In createProviderRequest - returning null");
+        Slog.d(TAG, "No options filtered");
         return null;
     }
 
     private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) {
         if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains(
                 componentName)) {
-            Log.d(TAG, "Provider allow list specified but does not contain this provider: "
-                    + componentName.flattenToString());
+            Slog.d(TAG, "Provider allow list specified but does not contain this provider");
             return false;
         }
         return true;
@@ -182,7 +180,7 @@
     private static boolean checkSystemProviderRequirement(CredentialOption option,
             boolean isSystemProvider) {
         if (option.isSystemProviderRequired() && !isSystemProvider) {
-            Log.d(TAG, "System provider required, but this service is not a system provider");
+            Slog.d(TAG, "System provider required, but this service is not a system provider");
             return false;
         }
         return true;
@@ -210,6 +208,7 @@
     /** Called when the provider response has been updated by an external source. */
     @Override // Callback from the remote provider
     public void onProviderResponseSuccess(@Nullable BeginGetCredentialResponse response) {
+        Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
         onSetInitialRemoteResponse(response);
     }
 
@@ -231,7 +230,7 @@
             updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
                     /*source=*/ CredentialsSource.REMOTE_PROVIDER);
         } else {
-            Slog.i(TAG, "Component names different in onProviderServiceDied - "
+            Slog.w(TAG, "Component names different in onProviderServiceDied - "
                     + "this should not happen");
         }
     }
@@ -244,13 +243,14 @@
     @Override // Selection call from the request provider
     protected void onUiEntrySelected(String entryType, String entryKey,
             ProviderPendingIntentResponse providerPendingIntentResponse) {
-        Log.i(TAG, "onUiEntrySelected with entryKey: " + entryKey);
+        Slog.d(TAG, "onUiEntrySelected with entryType: " + entryType + ", and entryKey: "
+                + entryKey);
         switch (entryType) {
             case CREDENTIAL_ENTRY_KEY:
                 CredentialEntry credentialEntry = mProviderResponseDataHandler
                         .getCredentialEntry(entryKey);
                 if (credentialEntry == null) {
-                    Log.i(TAG, "Unexpected credential entry key");
+                    Slog.w(TAG, "Unexpected credential entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
@@ -259,7 +259,7 @@
             case ACTION_ENTRY_KEY:
                 Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey);
                 if (actionEntry == null) {
-                    Log.i(TAG, "Unexpected action entry key");
+                    Slog.w(TAG, "Unexpected action entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
@@ -269,21 +269,21 @@
                 Action authenticationEntry = mProviderResponseDataHandler
                         .getAuthenticationAction(entryKey);
                 if (authenticationEntry == null) {
-                    Log.i(TAG, "Unexpected authenticationEntry key");
+                    Slog.w(TAG, "Unexpected authenticationEntry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
                 boolean additionalContentReceived =
                         onAuthenticationEntrySelected(providerPendingIntentResponse);
                 if (additionalContentReceived) {
-                    Log.i(TAG, "Additional content received - removing authentication entry");
+                    Slog.d(TAG, "Additional content received - removing authentication entry");
                     mProviderResponseDataHandler.removeAuthenticationAction(entryKey);
                     if (!mProviderResponseDataHandler.isEmptyResponse()) {
                         updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
                                 /*source=*/ CredentialsSource.AUTH_ENTRY);
                     }
                 } else {
-                    Log.i(TAG, "Additional content not received");
+                    Slog.d(TAG, "Additional content not received from authentication entry");
                     mProviderResponseDataHandler
                             .updateAuthEntryWithNoCredentialsReceived(entryKey);
                     updateStatusAndInvokeCallback(Status.NO_CREDENTIALS_FROM_AUTH_ENTRY,
@@ -294,12 +294,12 @@
                 if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) {
                     onRemoteEntrySelected(providerPendingIntentResponse);
                 } else {
-                    Log.i(TAG, "Unexpected remote entry key");
+                    Slog.d(TAG, "Unexpected remote entry key");
                     invokeCallbackOnInternalInvalidState();
                 }
                 break;
             default:
-                Log.i(TAG, "Unsupported entry type selected");
+                Slog.w(TAG, "Unsupported entry type selected");
                 invokeCallbackOnInternalInvalidState();
         }
     }
@@ -320,26 +320,24 @@
     @Override // Call from request session to data to be shown on the UI
     @Nullable
     protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
-        Log.i(TAG, "In prepareUiData");
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
-            Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
-                    + mComponentName.flattenToString());
+            Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
             return null;
         }
         if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
             return mProviderResponseDataHandler.toGetCredentialProviderData();
         }
-        Log.i(TAG, "In prepareUiData response null");
+        Slog.d(TAG, "In prepareUiData response null");
         return null;
     }
 
-    private Intent setUpFillInIntent(@NonNull String id) {
+    private Intent setUpFillInIntentWithFinalRequest(@NonNull String id) {
         // TODO: Determine if we should skip this entry if entry id is not set, or is set
         // but does not resolve to a valid option. For now, not skipping it because
         // it may be possible that the provider adds their own extras and expects to receive
         // those and complete the flow.
         if (mBeginGetOptionToCredentialOptionMap.get(id) == null) {
-            Log.i(TAG, "Id from Credential Entry does not resolve to a valid option");
+            Slog.w(TAG, "Id from Credential Entry does not resolve to a valid option");
             return new Intent();
         }
         return new Intent().putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
@@ -382,7 +380,8 @@
                     getCredentialResponse);
             return;
         }
-        Log.i(TAG, "Pending intent response contains no credential, or error");
+        Slog.d(TAG, "Pending intent response contains no credential, or error "
+                + "for a credential entry");
         invokeCallbackOnInternalInvalidState();
     }
 
@@ -390,14 +389,12 @@
     private GetCredentialException maybeGetPendingIntentException(
             ProviderPendingIntentResponse pendingIntentResponse) {
         if (pendingIntentResponse == null) {
-            Log.i(TAG, "pendingIntentResponse is null");
             return null;
         }
         if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
             GetCredentialException exception = PendingIntentResultHandler
                     .extractGetCredentialException(pendingIntentResponse.getResultData());
             if (exception != null) {
-                Log.i(TAG, "Pending intent contains provider exception");
                 return exception;
             }
         } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
@@ -463,7 +460,7 @@
     /** Returns true if either an exception or a response is found. */
     private void onActionEntrySelected(ProviderPendingIntentResponse
             providerPendingIntentResponse) {
-        Log.i(TAG, "onActionEntrySelected");
+        Slog.d(TAG, "onActionEntrySelected");
         onCredentialEntrySelected(providerPendingIntentResponse);
     }
 
@@ -559,7 +556,8 @@
             String id = generateUniqueId();
             Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
                     id, credentialEntry.getSlice(),
-                    setUpFillInIntent(credentialEntry.getBeginGetCredentialOptionId()));
+                    setUpFillInIntentWithFinalRequest(credentialEntry
+                            .getBeginGetCredentialOptionId()));
             mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry));
             mCredentialEntryTypes.add(credentialEntry.getType());
         }
@@ -574,9 +572,7 @@
 
         public void addAuthenticationAction(Action authenticationAction,
                 @AuthenticationEntry.Status int status) {
-            Log.i(TAG, "In addAuthenticationAction");
             String id = generateUniqueId();
-            Log.i(TAG, "In addAuthenticationAction, id : " + id);
             AuthenticationEntry entry = new AuthenticationEntry(
                     AUTHENTICATION_ACTION_ENTRY_KEY,
                     id, authenticationAction.getSlice(),
@@ -591,7 +587,7 @@
 
         public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) {
             if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) {
-                Log.i(TAG, "Remote entry being dropped as it does not meet the restriction"
+                Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction"
                         + " checks.");
                 return;
             }
@@ -715,7 +711,6 @@
                             == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT)
                     .findFirst();
             if (previousMostRecentAuthEntry.isEmpty()) {
-                Log.i(TAG, "In updatePreviousMostRecentAuthEntry - previous entry not found");
                 return;
             }
             String id = previousMostRecentAuthEntry.get().getKey();
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 24292ef2..c10f564 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -33,7 +33,7 @@
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.CredentialEntry;
 import android.service.credentials.CredentialProviderService;
-import android.telecom.Log;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -145,14 +145,12 @@
 
     private List<Entry> prepareUiCredentialEntries(
             @NonNull List<CredentialEntry> credentialEntries) {
-        Log.i(TAG, "in prepareUiProviderDataWithCredentials");
         List<Entry> credentialUiEntries = new ArrayList<>();
 
         // Populate the credential entries
         for (CredentialEntry credentialEntry : credentialEntries) {
             String entryId = generateUniqueId();
             mUiCredentialEntries.put(entryId, credentialEntry);
-            Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
             credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
                     credentialEntry.getSlice(),
                     setUpFillInIntent()));
@@ -172,15 +170,13 @@
 
     @Override
     protected ProviderData prepareUiData() {
-        Log.i(TAG, "In prepareUiData");
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
-            Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
-                    + mComponentName.flattenToString());
+            Slog.d(TAG, "No date for UI coming from: " + mComponentName.flattenToString());
             return null;
         }
         if (mProviderResponse == null) {
-            Log.i(TAG, "In prepareUiData response null");
-            throw new IllegalStateException("Response must be in completion mode");
+            Slog.w(TAG, "In prepareUiData but response is null. This is strange.");
+            return null;
         }
         return new GetCredentialProviderData.Builder(
                 mComponentName.flattenToString()).setActionChips(null)
@@ -200,13 +196,13 @@
             case CREDENTIAL_ENTRY_KEY:
                 CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
                 if (credentialEntry == null) {
-                    Log.i(TAG, "Unexpected credential entry key");
+                    Slog.w(TAG, "Unexpected credential entry key");
                     return;
                 }
                 onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
                 break;
             default:
-                Log.i(TAG, "Unsupported entry type selected");
+                Slog.w(TAG, "Unsupported entry type selected");
         }
     }
 
@@ -233,10 +229,8 @@
                 }
                 return;
             }
-
-            Log.i(TAG, "Pending intent response contains no credential, or error");
         }
-        Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
+        Slog.w(TAG, "CredentialEntry does not have a credential or a pending intent result");
     }
 
     @Override
@@ -279,14 +273,13 @@
     protected GetCredentialException maybeGetPendingIntentException(
             ProviderPendingIntentResponse pendingIntentResponse) {
         if (pendingIntentResponse == null) {
-            android.util.Log.i(TAG, "pendingIntentResponse is null");
             return null;
         }
         if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
             GetCredentialException exception = PendingIntentResultHandler
                     .extractGetCredentialException(pendingIntentResponse.getResultData());
             if (exception != null) {
-                android.util.Log.i(TAG, "Pending intent contains provider exception");
+                Slog.d(TAG, "Pending intent contains provider exception");
                 return exception;
             }
         } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index d165756..d02a8c1 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -29,7 +29,6 @@
 import android.credentials.ui.ProviderPendingIntentResponse;
 import android.os.ICancellationSignal;
 import android.os.RemoteException;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.server.credentials.metrics.ProviderSessionMetric;
@@ -253,7 +252,7 @@
             @Nullable ComponentName expectedRemoteEntryProviderService) {
         // Check if the service is the one set by the OEM. If not silently reject this entry
         if (!mComponentName.equals(expectedRemoteEntryProviderService)) {
-            Log.i(TAG, "Remote entry being dropped as it is not from the service "
+            Slog.w(TAG, "Remote entry being dropped as it is not from the service "
                     + "configured by the OEM.");
             return false;
         }
@@ -270,15 +269,12 @@
                 return true;
             }
         } catch (SecurityException e) {
-            Log.i(TAG, "Error getting info for "
-                    + mComponentName.flattenToString() + ": " + e.getMessage());
+            Slog.e(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
             return false;
         } catch (PackageManager.NameNotFoundException e) {
-            Log.i(TAG, "Error getting info for "
-                    + mComponentName.flattenToString() + ": " + e.getMessage());
+            Slog.i(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
             return false;
         }
-        Log.i(TAG, "In enforceRemoteEntryRestrictions - remote entry checks fail");
         return false;
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index f111a95..926c7e4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -27,6 +27,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.BroadcastOptions;
 import android.app.admin.DevicePolicyIdentifiers;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyState;
@@ -353,6 +354,7 @@
                     policyDefinition,
                     userId);
         }
+        sendDevicePolicyChangedToSystem(userId);
     }
 
     /**
@@ -478,6 +480,8 @@
                 enforcingAdmin,
                 policyDefinition,
                 UserHandle.USER_ALL);
+
+        sendDevicePolicyChangedToSystem(UserHandle.USER_ALL);
     }
 
     /**
@@ -699,7 +703,7 @@
 
         if (policyDefinition.isGlobalOnlyPolicy()) {
             throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a global only"
-                    + "policy.");
+                    + " policy.");
         }
 
         if (!mLocalPolicies.contains(userId)) {
@@ -724,7 +728,7 @@
     private <V> PolicyState<V> getGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {
         if (policyDefinition.isLocalOnlyPolicy()) {
             throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a local only"
-                    + "policy.");
+                    + " policy.");
         }
 
         if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) {
@@ -761,6 +765,20 @@
                 policyValue == null ? null : policyValue.getValue(), mContext, userId);
     }
 
+    private void sendDevicePolicyChangedToSystem(int userId) {
+        Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        Bundle options = new BroadcastOptions()
+                .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+                .toBundle();
+        Binder.withCleanCallingIdentity(() -> mContext.sendBroadcastAsUser(
+                intent,
+                new UserHandle(userId),
+                /* receiverPermissions= */ null,
+                options));
+    }
+
     private <V> void sendPolicyResultToAdmin(
             EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, int result, int userId) {
         Intent intent = new Intent(PolicyUpdateReceiver.ACTION_DEVICE_POLICY_SET_RESULT);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 94e6e73..29f9a30 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.BIND_DEVICE_ADMIN;
 import static android.Manifest.permission.LOCK_DEVICE;
 import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
+import static android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL;
@@ -59,6 +60,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SAFE_BOOT;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CAPTURE;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CONTENT;
@@ -441,7 +443,6 @@
 import android.util.DebugUtils;
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
-import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -7767,12 +7768,14 @@
                 // Explicit behaviour
                 if (factoryReset) {
                     // TODO(b/254031494) Replace with new factory reset permission checks
-                    boolean hasPermission = isDeviceOwnerUserId(userId)
-                            || (isOrganizationOwnedDeviceWithManagedProfile()
-                            && calledOnParentInstance);
-                    Preconditions.checkState(hasPermission,
-                            "Admin %s does not have permission to factory reset the device.",
-                            userId);
+                    if (!isPermissionCheckFlagEnabled()) {
+                        boolean hasPermission = isDeviceOwnerUserId(userId)
+                                || (isOrganizationOwnedDeviceWithManagedProfile()
+                                && calledOnParentInstance);
+                        Preconditions.checkCallAuthorization(hasPermission,
+                                "Admin %s does not have permission to factory reset the device.",
+                                userId);
+                    }
                     wipeDevice = true;
                 } else {
                     Preconditions.checkCallAuthorization(!isSystemUser,
@@ -13131,19 +13134,22 @@
         }
         int userId = caller.getUserId();
 
-        if (!UserRestrictionsUtils.isValidRestriction(key)) {
-            return;
-        }
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
 
         if (isPolicyEngineForFinanceFlagEnabled()) {
-            int affectedUserId = parent ? getProfileParentId(userId) : userId;
-            EnforcingAdmin admin = enforcePermissionForUserRestriction(
-                    who,
-                    key,
-                    caller.getPackageName(),
-                    affectedUserId);
-            if (mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, callerPackage, userId)) {
+            if (!isDeviceOwner(caller) && !isProfileOwner(caller)) {
+                if (!mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, callerPackage, userId)) {
+                    throw new IllegalStateException("Calling package is not targeting Android U.");
+                }
+                if (!UserRestrictionsUtils.isValidRestriction(key)) {
+                    throw new IllegalArgumentException("Invalid restriction key: " + key);
+                }
+                int affectedUserId = parent ? getProfileParentId(userId) : userId;
+                EnforcingAdmin admin = enforcePermissionForUserRestriction(
+                        who,
+                        key,
+                        caller.getPackageName(),
+                        affectedUserId);
                 PolicyDefinition<Boolean> policyDefinition =
                         PolicyDefinition.getPolicyDefinitionForUserRestriction(key);
                 if (enabledFromThisOwner) {
@@ -13155,7 +13161,8 @@
                         setGlobalUserRestrictionInternal(admin, key, /* enabled= */ false);
                     }
                     if (!policyDefinition.isGlobalOnlyPolicy()) {
-                        setLocalUserRestrictionInternal(admin, key, /* enabled= */ false, userId);
+                        setLocalUserRestrictionInternal(admin, key, /* enabled= */ false,
+                                userId);
 
                         int parentUserId = getProfileParentId(userId);
                         if (parentUserId != userId) {
@@ -13165,49 +13172,21 @@
                     }
                 }
             } else {
+                if (!UserRestrictionsUtils.isValidRestriction(key)) {
+                    return;
+                }
+                Objects.requireNonNull(who, "ComponentName is null");
+                EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage);
+                checkAdminCanSetRestriction(caller, parent, key);
                 setBackwardCompatibleUserRestriction(
                         caller, admin, key, enabledFromThisOwner, parent);
             }
         } else {
+            if (!UserRestrictionsUtils.isValidRestriction(key)) {
+                return;
+            }
             Objects.requireNonNull(who, "ComponentName is null");
-            if (parent) {
-                Preconditions.checkCallAuthorization(
-                        isProfileOwnerOfOrganizationOwnedDevice(caller));
-            } else {
-                Preconditions.checkCallAuthorization(
-                        isDeviceOwner(caller) || isProfileOwner(caller));
-            }
-            synchronized (getLockObject()) {
-                if (isDefaultDeviceOwner(caller)) {
-                    if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
-                        throw new SecurityException("Device owner cannot set user restriction "
-                                + key);
-                    }
-                    Preconditions.checkArgument(!parent,
-                            "Cannot use the parent instance in Device Owner mode");
-                } else if (isFinancedDeviceOwner(caller)) {
-                    if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) {
-                        throw new SecurityException("Cannot set user restriction " + key
-                                + " when managing a financed device");
-                    }
-                    Preconditions.checkArgument(!parent,
-                            "Cannot use the parent instance in Financed Device Owner"
-                                    + " mode");
-                } else {
-                    boolean profileOwnerCanChangeOnItself = !parent
-                            && UserRestrictionsUtils.canProfileOwnerChange(
-                            key, userId == getMainUserId());
-                    boolean orgOwnedProfileOwnerCanChangeGlobally = parent
-                            && isProfileOwnerOfOrganizationOwnedDevice(caller)
-                            && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(
-                            key);
-
-                    if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangeGlobally) {
-                        throw new SecurityException("Profile owner cannot set user restriction "
-                                + key);
-                    }
-                }
-            }
+            checkAdminCanSetRestriction(caller, parent, key);
             synchronized (getLockObject()) {
                 final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
                         getProfileOwnerOrDeviceOwnerLocked(userId), parent);
@@ -13224,6 +13203,46 @@
         logUserRestrictionCall(key, enabledFromThisOwner, parent, caller);
     }
 
+    private void checkAdminCanSetRestriction(CallerIdentity caller, boolean parent, String key) {
+        if (parent) {
+            Preconditions.checkCallAuthorization(
+                    isProfileOwnerOfOrganizationOwnedDevice(caller));
+        } else {
+            Preconditions.checkCallAuthorization(
+                    isDeviceOwner(caller) || isProfileOwner(caller));
+        }
+        synchronized (getLockObject()) {
+            if (isDefaultDeviceOwner(caller)) {
+                if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
+                    throw new SecurityException("Device owner cannot set user restriction "
+                            + key);
+                }
+                Preconditions.checkArgument(!parent,
+                        "Cannot use the parent instance in Device Owner mode");
+            } else if (isFinancedDeviceOwner(caller)) {
+                if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) {
+                    throw new SecurityException("Cannot set user restriction " + key
+                            + " when managing a financed device");
+                }
+                Preconditions.checkArgument(!parent,
+                        "Cannot use the parent instance in Financed Device Owner"
+                                + " mode");
+            } else {
+                boolean profileOwnerCanChangeOnItself = !parent
+                        && UserRestrictionsUtils.canProfileOwnerChange(
+                        key, caller.getUserId() == getMainUserId());
+                boolean orgOwnedProfileOwnerCanChangeGlobally = parent
+                        && isProfileOwnerOfOrganizationOwnedDevice(caller)
+                        && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(
+                        key);
+
+                if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangeGlobally) {
+                    throw new SecurityException("Profile owner cannot set user restriction "
+                            + key);
+                }
+            }
+        }
+    }
     private void setBackwardCompatibleUserRestriction(
             CallerIdentity caller, EnforcingAdmin admin, String key, boolean enabled,
             boolean parent) {
@@ -13252,20 +13271,22 @@
     @Override
     public void setUserRestrictionGlobally(String callerPackage, String key) {
         final CallerIdentity caller = getCallerIdentity(callerPackage);
-        if (!UserRestrictionsUtils.isValidRestriction(key)) {
-            return;
-        }
 
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
 
         if (!isPolicyEngineForFinanceFlagEnabled()) {
             throw new IllegalStateException("Feature flag is not enabled.");
         }
-
+        if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+            throw new IllegalStateException("Admins are not allowed to call this API.");
+        }
         if (!mInjector.isChangeEnabled(
                 ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
             throw new IllegalStateException("Calling package is not targeting Android U.");
         }
+        if (!UserRestrictionsUtils.isValidRestriction(key)) {
+            throw new IllegalArgumentException("Invalid restriction key: " + key);
+        }
 
         EnforcingAdmin admin = enforcePermissionForUserRestriction(
                 /* who= */ null,
@@ -13416,14 +13437,25 @@
             int targetUserId = parent
                     ? getProfileParentId(caller.getUserId()) : caller.getUserId();
             EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage);
-            Bundle restrictions = getUserRestrictionsFromPolicyEngine(admin, targetUserId);
-            // Add global restrictions set by the admin as well if admin is not targeting Android U.
-            if (!mInjector.isChangeEnabled(
-                    ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
+            if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+                Objects.requireNonNull(who, "ComponentName is null");
+                Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                        || isFinancedDeviceOwner(caller)
+                        || isProfileOwner(caller)
+                        || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
+
+                Bundle restrictions = getUserRestrictionsFromPolicyEngine(admin, targetUserId);
+                // Add global restrictions set by the admin as well.
                 restrictions.putAll(
                         getUserRestrictionsFromPolicyEngine(admin, UserHandle.USER_ALL));
+                return restrictions;
+            } else {
+                if (!mInjector.isChangeEnabled(
+                        ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
+                    throw new IllegalStateException("Calling package is not targeting Android U.");
+                }
+                return getUserRestrictionsFromPolicyEngine(admin, targetUserId);
             }
-            return restrictions;
         } else {
             Objects.requireNonNull(who, "ComponentName is null");
             Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
@@ -13439,164 +13471,162 @@
     }
 
     // Map of user restriction to permission.
-    private static final HashMap<String, String> USER_RESTRICTION_PERMISSIONS = new HashMap<>();
+    private static final HashMap<String, String[]> USER_RESTRICTION_PERMISSIONS = new HashMap<>();
     {
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.ENSURE_VERIFY_APPS, MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+                UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_WIFI_TETHERING, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_ADD_CLONE_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_WIFI_DIRECT, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_USER_SWITCH, MANAGE_DEVICE_POLICY_USERS);
+                UserManager.DISALLOW_ADD_WIFI_CONFIG, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_USB_FILE_TRANSFER, MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER);
+                UserManager.DISALLOW_ADJUST_VOLUME, new String[]{MANAGE_DEVICE_POLICY_AUDIO_OUTPUT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_UNMUTE_MICROPHONE, MANAGE_DEVICE_POLICY_MICROPHONE);
+                UserManager.DISALLOW_AIRPLANE_MODE, new String[]{MANAGE_DEVICE_POLICY_AIRPLANE_MODE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_UNMUTE_DEVICE, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT);
+                UserManager.DISALLOW_AMBIENT_DISPLAY, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_UNINSTALL_APPS, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+                UserManager.DISALLOW_APPS_CONTROL, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_UNIFIED_PASSWORD, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+                UserManager.DISALLOW_AUTOFILL, new String[]{MANAGE_DEVICE_POLICY_AUTOFILL});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS);
+                UserManager.DISALLOW_BLUETOOTH, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SMS, MANAGE_DEVICE_POLICY_SMS);
+                UserManager.DISALLOW_BLUETOOTH_SHARING, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_CAMERA, new String[]{MANAGE_DEVICE_POLICY_CAMERA});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SHARE_LOCATION, MANAGE_DEVICE_POLICY_LOCATION);
+                UserManager.DISALLOW_CAMERA_TOGGLE, new String[]{MANAGE_DEVICE_POLICY_CAMERA_TOGGLE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
-                MANAGE_DEVICE_POLICY_PROFILE_INTERACTION);
+                UserManager.DISALLOW_CELLULAR_2G, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SET_WALLPAPER, MANAGE_DEVICE_POLICY_WALLPAPER);
+                UserManager.DISALLOW_CHANGE_WIFI_STATE, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SET_USER_ICON, MANAGE_DEVICE_POLICY_USERS);
+                UserManager.DISALLOW_CONFIG_BLUETOOTH, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SAFE_BOOT, MANAGE_DEVICE_POLICY_SAFE_BOOT);
+                UserManager.DISALLOW_CONFIG_BRIGHTNESS, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_RUN_IN_BACKGROUND, MANAGE_DEVICE_POLICY_SAFE_BOOT);
+                UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_REMOVE_USER, MANAGE_DEVICE_POLICY_USERS);
+                UserManager.DISALLOW_CONFIG_CREDENTIALS, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_PRINTING, MANAGE_DEVICE_POLICY_PRINTING);
+                UserManager.DISALLOW_CONFIG_DATE_TIME, new String[]{MANAGE_DEVICE_POLICY_TIME});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_OUTGOING_CALLS, MANAGE_DEVICE_POLICY_CALLS);
+                UserManager.DISALLOW_CONFIG_DEFAULT_APPS, new String[]{MANAGE_DEFAULT_APPLICATIONS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_OUTGOING_BEAM, MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION);
+                UserManager.DISALLOW_CONFIG_LOCALE, new String[]{MANAGE_DEVICE_POLICY_LOCALE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_NETWORK_RESET, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_CONFIG_LOCATION, new String[]{MANAGE_DEVICE_POLICY_LOCATION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA);
+                UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_MODIFY_ACCOUNTS, MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT);
+                UserManager.DISALLOW_CONFIG_PRIVATE_DNS, new String[]{MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_MICROPHONE_TOGGLE, MANAGE_DEVICE_POLICY_MICROPHONE);
+                UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
-                MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+                UserManager.DISALLOW_CONFIG_TETHERING, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
-                MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+                UserManager.DISALLOW_CONFIG_VPN, new String[]{MANAGE_DEVICE_POLICY_VPN});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_INSTALL_APPS, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+                UserManager.DISALLOW_CONFIG_WIFI, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_FUN, MANAGE_DEVICE_POLICY_FUN);
+                UserManager.DISALLOW_CONTENT_CAPTURE, new String[]{MANAGE_DEVICE_POLICY_SCREEN_CONTENT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_FACTORY_RESET, MANAGE_DEVICE_POLICY_FACTORY_RESET);
+                UserManager.DISALLOW_CONTENT_SUGGESTIONS, new String[]{MANAGE_DEVICE_POLICY_SCREEN_CONTENT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_DEBUGGING_FEATURES, MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES);
+                UserManager.DISALLOW_CREATE_WINDOWS, new String[]{MANAGE_DEVICE_POLICY_WINDOWS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_DATA_ROAMING, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, new String[]{MANAGE_DEVICE_POLICY_PROFILE_INTERACTION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
-                MANAGE_DEVICE_POLICY_PROFILE_INTERACTION);
+                UserManager.DISALLOW_DATA_ROAMING, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CREATE_WINDOWS, MANAGE_DEVICE_POLICY_WINDOWS);
+                UserManager.DISALLOW_DEBUGGING_FEATURES, new String[]{MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONTENT_SUGGESTIONS, MANAGE_DEVICE_POLICY_SCREEN_CONTENT);
+                UserManager.DISALLOW_FACTORY_RESET, new String[]{MANAGE_DEVICE_POLICY_FACTORY_RESET});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONTENT_CAPTURE, MANAGE_DEVICE_POLICY_SCREEN_CONTENT);
+                UserManager.DISALLOW_FUN, new String[]{MANAGE_DEVICE_POLICY_FUN});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_WIFI, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_INSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_VPN, MANAGE_DEVICE_POLICY_VPN);
+                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_TETHERING, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, MANAGE_DEVICE_POLICY_DISPLAY);
+                UserManager.DISALLOW_MICROPHONE_TOGGLE, new String[]{MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_PRIVATE_DNS, MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS);
+                UserManager.DISALLOW_MODIFY_ACCOUNTS, new String[]{MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, new String[]{MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_LOCATION, MANAGE_DEVICE_POLICY_LOCATION);
+                UserManager.DISALLOW_NETWORK_RESET, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_LOCALE, MANAGE_DEVICE_POLICY_LOCALE);
+                UserManager.DISALLOW_OUTGOING_BEAM, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_DATE_TIME, MANAGE_DEVICE_POLICY_TIME);
+                UserManager.DISALLOW_OUTGOING_CALLS, new String[]{MANAGE_DEVICE_POLICY_CALLS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_CREDENTIALS, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+                UserManager.DISALLOW_PRINTING, new String[]{MANAGE_DEVICE_POLICY_PRINTING});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_BRIGHTNESS, MANAGE_DEVICE_POLICY_DISPLAY);
+                UserManager.DISALLOW_RUN_IN_BACKGROUND, new String[]{MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_BLUETOOTH, MANAGE_DEVICE_POLICY_BLUETOOTH);
+                UserManager.DISALLOW_SAFE_BOOT, new String[]{MANAGE_DEVICE_POLICY_SAFE_BOOT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CHANGE_WIFI_STATE, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CAMERA_TOGGLE, MANAGE_DEVICE_POLICY_CAMERA);
+                UserManager.DISALLOW_SET_WALLPAPER, new String[]{MANAGE_DEVICE_POLICY_WALLPAPER});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CAMERA, MANAGE_DEVICE_POLICY_CAMERA);
+                UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILE_INTERACTION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_BLUETOOTH_SHARING, MANAGE_DEVICE_POLICY_BLUETOOTH);
+                UserManager.DISALLOW_SHARE_LOCATION, new String[]{MANAGE_DEVICE_POLICY_LOCATION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_BLUETOOTH, MANAGE_DEVICE_POLICY_BLUETOOTH);
+                UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_BIOMETRIC, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+                UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_AUTOFILL, MANAGE_DEVICE_POLICY_AUTOFILL);
+                UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_APPS_CONTROL, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+                UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_AMBIENT_DISPLAY, MANAGE_DEVICE_POLICY_DISPLAY);
+                UserManager.DISALLOW_UNIFIED_PASSWORD, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_AIRPLANE_MODE, MANAGE_DEVICE_POLICY_AIRPLANE_MODE);
+                UserManager.DISALLOW_UNINSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ADJUST_VOLUME, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT);
+                UserManager.DISALLOW_UNMUTE_DEVICE, new String[]{MANAGE_DEVICE_POLICY_AUDIO_OUTPUT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ADD_WIFI_CONFIG, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_UNMUTE_MICROPHONE, new String[]{MANAGE_DEVICE_POLICY_MICROPHONE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ADD_USER, MANAGE_DEVICE_POLICY_USERS);
+                UserManager.DISALLOW_USB_FILE_TRANSFER, new String[]{MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ADD_CLONE_PROFILE, MANAGE_DEVICE_POLICY_PROFILES);
+                UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, MANAGE_DEVICE_POLICY_PROFILES);
+                UserManager.DISALLOW_WIFI_DIRECT, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CELLULAR_2G, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_WIFI_TETHERING, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
-                MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION);
+                UserManager.ENSURE_VERIFY_APPS, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
 
         // Restrictions not allowed to be set by admins.
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_RECORD_AUDIO, null);
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_WALLPAPER, null);
-        USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_DEFAULT_APPS, null);
     }
 
     private EnforcingAdmin enforcePermissionForUserRestriction(ComponentName who,
             String userRestriction, String callerPackageName, int userId) {
-        String permission = USER_RESTRICTION_PERMISSIONS.get(userRestriction);
-        if (permission != null) {
-            return enforcePermissionAndGetEnforcingAdmin(who, permission, callerPackageName,
-                    userId);
-        }
+        String[] permissions = USER_RESTRICTION_PERMISSIONS.get(userRestriction);
+        if (permissions.length > 0) {
+            try {
+                return enforcePermissionsAndGetEnforcingAdmin(who, permissions, callerPackageName,
+                        userId);
+            } catch (SecurityException e) {
+                throw new SecurityException("Caller does not hold the required permission for this "
+                        + "user restriction: " + userRestriction + ".\n" + e.getMessage());
+            }
+         }
         throw new SecurityException("Admins are not permitted to set User Restriction: "
                 + userRestriction);
     }
@@ -14017,9 +14047,12 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
 
         if (isPolicyEngineForFinanceFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+            EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
                     who,
-                    MANAGE_DEVICE_POLICY_APPS_CONTROL,
+                    new String[]{
+                            MANAGE_DEVICE_POLICY_APPS_CONTROL,
+                            MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL
+                    },
                     caller.getPackageName(),
                     caller.getUserId());
             mDevicePolicyEngine.setLocalPolicy(
@@ -16626,10 +16659,6 @@
         SENSOR_PERMISSIONS.add(Manifest.permission.BACKGROUND_CAMERA);
         SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_BACKGROUND_AUDIO);
         SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
-        SENSOR_PERMISSIONS.add(
-                Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE);
-        SENSOR_PERMISSIONS.add(
-                    Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND);
     }
 
     private boolean canGrantPermission(CallerIdentity caller, String permission,
@@ -22422,6 +22451,14 @@
         });
     }
 
+    // Permission that will need to be created in V.
+    private static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL =
+            "manage_device_policy_block_uninstall";
+    private static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE =
+            "manage_device_policy_camera_toggle";
+    private static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE =
+            "manage_device_policy_microphone_toggle";
+
     // DPC types
     private static final int DEFAULT_DEVICE_OWNER = 0;
     private static final int FINANCED_DEVICE_OWNER = 1;
@@ -22645,7 +22682,7 @@
     {
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, DELEGATION_PERMISSION_GRANT);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, DELEGATION_APP_RESTRICTIONS);
-        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APPS_CONTROL, DELEGATION_BLOCK_UNINSTALL);
+        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL, DELEGATION_BLOCK_UNINSTALL);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, DELEGATION_SECURITY_LOGGING);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE, DELEGATION_PACKAGE_ACCESS);
     }
@@ -22724,6 +22761,10 @@
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUTOFILL,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL,
+                MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CAMERA_TOGGLE,
+                MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
@@ -22740,6 +22781,8 @@
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE,
+                MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PROFILES,
@@ -22770,13 +22813,34 @@
 
     /**
      * Checks if the calling process has been granted permission to apply a device policy on a
+     * specific user. Only one permission provided in the list needs to be granted to pass this
+     * check.
+     * The given permissions will be checked along with their associated cross-user permissions if
+     * they exists and the target user is different to the calling user.
+     * Returns an {@link EnforcingAdmin} for the caller.
+     *
+     * @param admin the component name of the admin.
+     * @param callerPackageName The package name of the calling application.
+     * @param permissions an array of permission names to be checked.
+     * @param targetUserId The userId of the user which the caller needs permission to act on.
+     * @throws SecurityException if the caller has not been granted the given permission,
+     * the associated cross-user permission if the caller's user is different to the target user.
+     */
+    private EnforcingAdmin enforcePermissionsAndGetEnforcingAdmin(@Nullable ComponentName admin,
+            String[] permissions, String callerPackageName, int targetUserId) {
+        enforcePermissions(permissions, callerPackageName, targetUserId);
+        return getEnforcingAdminForCaller(admin, callerPackageName);
+    }
+
+    /**
+     * Checks if the calling process has been granted permission to apply a device policy on a
      * specific user.
      * The given permission will be checked along with its associated cross-user permission if it
      * exists and the target user is different to the calling user.
      * Returns an {@link EnforcingAdmin} for the caller.
      *
      * @param admin the component name of the admin.
-     * @param callerPackageName The package name  of the calling application.
+     * @param callerPackageName The package name of the calling application.
      * @param permission The name of the permission being checked.
      * @param targetUserId The userId of the user which the caller needs permission to act on.
      * @throws SecurityException if the caller has not been granted the given permission,
@@ -22834,32 +22898,24 @@
             new HashMap<>();
 
     /**
-     * Checks if the calling process has been granted permission to apply a device policy on a
-     * specific user.
-     * The given permission will be checked along with its associated cross-user permission if it
-     * exists and the target user is different to the calling user.
+     * Checks if the calling process has been granted permission to apply a device policy.
      *
      * @param callerPackageName The package name  of the calling application.
      * @param permission The name of the permission being checked.
-     * @param targetUserId The userId of the user which the caller needs permission to act on.
      * @throws SecurityException if the caller has not been granted the given permission,
      * the associated cross-user permission if the caller's user is different to the target user.
      */
-    private void enforcePermission(String permission, String callerPackageName, int targetUserId)
+    private void enforcePermission(String permission, String callerPackageName)
             throws SecurityException {
-        if (!hasPermission(permission, callerPackageName, targetUserId)) {
-            // TODO(b/276920002): Split the error messages so that the cross-user permission
-            // is only mentioned when it is needed.
+        if (!hasPermission(permission, callerPackageName)) {
             throw new SecurityException("Caller does not have the required permissions for "
-                    + "this user. Permissions required: {"
+                    + "this user. Permission required: "
                     + permission
-                    + ", "
-                    + CROSS_USER_PERMISSIONS.get(permission)
-                    + "(if calling cross-user)"
-                    + "}");
+                    + ".");
         }
     }
 
+
     /**
      * Checks if the calling process has been granted permission to apply a device policy on a
      * specific user.
@@ -22872,23 +22928,66 @@
      * @throws SecurityException if the caller has not been granted the given permission,
      * the associated cross-user permission if the caller's user is different to the target user.
      */
-    private void enforcePermission(String permission, int adminPolicy,
-            String callerPackageName, int targetUserId)
+    private void enforcePermission(String permission, String callerPackageName, int targetUserId)
             throws SecurityException {
-        if (!hasPermissionOrAdminPolicy(permission, callerPackageName, adminPolicy, targetUserId)) {
-            // TODO(b/276920002): Split the error messages so that the cross-user permission
-            // is only mentioned when it is needed.
-            throw new SecurityException("Caller does not have the required permissions for "
-                    + "this user. Permissions required: {"
-                    + permission
-                    + ", "
-                    + CROSS_USER_PERMISSIONS.get(permission)
-                    + "(if calling cross-user)"
-                    + "}");
+        enforcePermission(permission, callerPackageName);
+        if (targetUserId != getCallerIdentity(callerPackageName).getUserId()) {
+            enforcePermission(CROSS_USER_PERMISSIONS.get(permission), callerPackageName);
         }
     }
 
     /**
+     * Checks if the calling process has been granted permission to apply a device policy on a
+     * specific user. Only one of the given permissions will be required to be held to pass this
+     * check.
+     * The given permissions will be checked along with their associated cross-user permissions if
+     * they exist and the target user is different to the calling user.
+     *
+     * @param permissions An array of the names of the permissions being checked.
+     * @param callerPackageName The package name  of the calling application.
+     * @param targetUserId The userId of the user which the caller needs permission to act on.
+     * @throws SecurityException if the caller has not been granted the given permission,
+     * the associated cross-user permission if the caller's user is different to the target user.
+     */
+    private void enforcePermissions(String[] permissions, String callerPackageName,
+            int targetUserId) throws SecurityException {
+        String heldPermission = "";
+        for (String permission : permissions) {
+            if (hasPermission(permission, callerPackageName)) {
+                heldPermission = permission;
+                break;
+            }
+        }
+        if (heldPermission.isEmpty()) {
+            throw new SecurityException("Caller does not have the required permissions for "
+                    + "this user. One of the following permission required: "
+                    + Arrays.toString(permissions));
+        }
+        enforcePermission(heldPermission, callerPackageName, targetUserId);
+    }
+
+    /**
+     * Checks if the calling process has been granted permission to apply a device policy on a
+     * specific user.
+     * The given permission will be checked along with its associated cross-user permission if it
+     * exists and the target user is different to the calling user.
+     *
+     * @param callerPackageName The package name  of the calling application.
+     * @param adminPolicy The admin policy that should grant holders permission.
+     * @param permission The name of the permission being checked.
+     * @param targetUserId The userId of the user which the caller needs permission to act on.
+     * @throws SecurityException if the caller has not been granted the given permission,
+     * the associated cross-user permission if the caller's user is different to the target user.
+     */
+    private void enforcePermission(String permission, int adminPolicy,
+            String callerPackageName, int targetUserId) throws SecurityException {
+        if (hasAdminPolicy(adminPolicy, callerPackageName)) {
+            return;
+        }
+        enforcePermission(permission, callerPackageName, targetUserId);
+    }
+
+    /**
      * Checks whether the calling process has been granted permission to query a device policy on
      * a specific user.
      * The given permission will be checked along with its associated cross-user permission if it
@@ -22909,6 +23008,12 @@
         enforcePermission(permission, callerPackageName, targetUserId);
     }
 
+    private boolean hasAdminPolicy(int adminPolicy, String callerPackageName) {
+        CallerIdentity caller = getCallerIdentity(callerPackageName);
+        ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller);
+        return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy);
+    }
+
     /**
      * Return whether the calling process has been granted permission to apply a device policy on
      * a specific user.
@@ -22921,24 +23026,15 @@
         CallerIdentity caller = getCallerIdentity(callerPackageName);
         boolean hasPermissionOnOwnUser = hasPermission(permission, caller.getPackageName());
         boolean hasPermissionOnTargetUser = true;
-        if (hasPermissionOnOwnUser & caller.getUserId() != targetUserId) {
-            hasPermissionOnTargetUser = hasPermission(CROSS_USER_PERMISSIONS.get(permission),
-                    caller.getPackageName());
+        if (hasPermissionOnOwnUser && caller.getUserId() != targetUserId) {
+            hasPermissionOnTargetUser = hasPermissionOnTargetUser
+                    && hasPermission(CROSS_USER_PERMISSIONS.get(permission),
+                        caller.getPackageName());
         }
 
         return hasPermissionOnOwnUser && hasPermissionOnTargetUser;
     }
 
-    private boolean hasPermissionOrAdminPolicy(String permission, String callerPackageName,
-            int adminPolicy, int targetUserId) {
-        CallerIdentity caller = getCallerIdentity(callerPackageName);
-        if (hasPermission(permission, caller.getPackageName(), targetUserId)) {
-            return true;
-        }
-        ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller);
-        return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy);
-    }
-
     /**
      * Return whether the calling process has been granted the given permission.
      *
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 509a66b..8c2468a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -284,6 +284,7 @@
     private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
     private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
 
+    // TODO(b/277218360): Revisit policies that should be marked as global-only.
     static {
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY,
@@ -312,8 +313,9 @@
         USER_RESTRICTION_FLAGS.put(
                 UserManager.DISALLOW_WIFI_TETHERING, POLICY_FLAG_GLOBAL_ONLY_POLICY);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_GRANT_ADMIN, /* flags= */ 0);
+        // TODO: set as global only once we get rid of the mapping
         USER_RESTRICTION_FLAGS.put(
-                UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+                UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(
                 UserManager.DISALLOW_WIFI_DIRECT, POLICY_FLAG_GLOBAL_ONLY_POLICY);
         USER_RESTRICTION_FLAGS.put(
@@ -333,8 +335,10 @@
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_BLUETOOTH, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BLUETOOTH, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BLUETOOTH_SHARING, /* flags= */ 0);
+        // This effectively always applies globally, but it can be set on the profile
+        // parent, check the javadocs on the restriction for more info.
         USER_RESTRICTION_FLAGS.put(
-                UserManager.DISALLOW_USB_FILE_TRANSFER, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+                UserManager.DISALLOW_USB_FILE_TRANSFER, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_CREDENTIALS, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_REMOVE_USER, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, /* flags= */ 0);
@@ -344,8 +348,10 @@
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_DATE_TIME, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(
                 UserManager.DISALLOW_CONFIG_TETHERING, /* flags= */ 0);
+        // This effectively always applies globally, but it can be set on the profile
+        // parent, check the javadocs on the restriction for more info.
         USER_RESTRICTION_FLAGS.put(
-                UserManager.DISALLOW_NETWORK_RESET, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+                UserManager.DISALLOW_NETWORK_RESET, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_FACTORY_RESET, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_USER, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_MANAGED_PROFILE, /* flags= */ 0);
@@ -376,8 +382,7 @@
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNMUTE_DEVICE, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_DATA_ROAMING, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_SET_USER_ICON, /* flags= */ 0);
-            // TODO: double check flags
-        USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OEM_UNLOCK, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+        USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OEM_UNLOCK, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNIFIED_PASSWORD, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_AUTOFILL, /* flags= */ 0);
@@ -390,6 +395,7 @@
         USER_RESTRICTION_FLAGS.put(
                 UserManager.DISALLOW_CONFIG_PRIVATE_DNS, POLICY_FLAG_GLOBAL_ONLY_POLICY);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MICROPHONE_TOGGLE, /* flags= */ 0);
+        // TODO: According the UserRestrictionsUtils, this is global only, need to confirm.
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CAMERA_TOGGLE, /* flags= */ 0);
         // TODO: check if its global only
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BIOMETRIC, /* flags= */ 0);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 492d477..b1d6131 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -125,6 +125,7 @@
 import com.android.server.connectivity.PacProxyService;
 import com.android.server.contentcapture.ContentCaptureManagerInternal;
 import com.android.server.coverage.CoverageService;
+import com.android.server.cpu.CpuMonitorService;
 import com.android.server.devicepolicy.DevicePolicyManagerService;
 import com.android.server.devicestate.DeviceStateManagerService;
 import com.android.server.display.DisplayManagerService;
@@ -1405,6 +1406,15 @@
         mSystemServiceManager.startService(RemoteProvisioningService.class);
         t.traceEnd();
 
+        // TODO(b/277600174): Start CpuMonitorService on all builds and not just on debuggable
+        // builds once the Android JobScheduler starts using this service.
+        if (Build.IS_DEBUGGABLE || Build.IS_ENG) {
+          // Service for CPU monitor.
+          t.traceBegin("CpuMonitorService");
+          mSystemServiceManager.startService(CpuMonitorService.class);
+          t.traceEnd();
+        }
+
         t.traceEnd(); // startCoreServices
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 1a75170..7b771af 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -567,36 +567,36 @@
 
         // Ensure that no action is taken for cases where the failure reason is unknown
         assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN, 1),
-                PackageHealthObserverImpact.USER_IMPACT_NONE);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
 
         // Ensure the correct user impact is returned for each mitigation count.
         assertEquals(observer.onHealthCheckFailed(null,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
-                PackageHealthObserverImpact.USER_IMPACT_LOW);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
 
         assertEquals(observer.onHealthCheckFailed(null,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
-                PackageHealthObserverImpact.USER_IMPACT_LOW);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
 
         assertEquals(observer.onHealthCheckFailed(null,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3),
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
 
         assertEquals(observer.onHealthCheckFailed(null,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4),
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
     }
 
     @Test
     public void testBootLoopLevels() {
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
 
-        assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_NONE);
-        assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LOW);
-        assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LOW);
-        assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_HIGH);
-        assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_HIGH);
-        assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_HIGH);
+        assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
+        assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
+        assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
+        assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+        assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+        assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 8211d6f..bc3f5ab 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -237,9 +237,23 @@
     }
 
     private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
+            BroadcastRecord record, int recordIndex) {
+        enqueueOrReplaceBroadcast(queue, record, recordIndex, false, 42_000_000L);
+    }
+
+    private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
             BroadcastRecord record, int recordIndex, long enqueueTime) {
-        queue.enqueueOrReplaceBroadcast(record, recordIndex, false);
+        enqueueOrReplaceBroadcast(queue, record, recordIndex, false, enqueueTime);
+    }
+
+    private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
+            BroadcastRecord record, int recordIndex, boolean wouldBeSkipped, long enqueueTime) {
+        queue.enqueueOrReplaceBroadcast(record, recordIndex, wouldBeSkipped, (r, i) -> {
+            throw new UnsupportedOperationException();
+        });
         record.enqueueTime = enqueueTime;
+        record.enqueueRealTime = enqueueTime;
+        record.enqueueClockTime = enqueueTime;
     }
 
     @Test
@@ -370,7 +384,7 @@
                 .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options,
                 List.of(makeMockRegisteredReceiver()), false);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
 
         queue.setProcessAndUidCached(null, false);
         final long notCachedRunnableAt = queue.getRunnableAt();
@@ -397,7 +411,7 @@
                 .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options,
                 List.of(makeMockRegisteredReceiver()), false);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
 
         queue.setProcessAndUidCached(null, false);
         final long notCachedRunnableAt = queue.getRunnableAt();
@@ -421,12 +435,12 @@
         // enqueue a bg-priority broadcast then a fg-priority one
         final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
         final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
-        queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, timezoneRecord, 0);
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
 
         // verify that:
         // (a) the queue is immediately runnable by existence of a fg-priority broadcast
@@ -457,13 +471,14 @@
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
                 List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
                         withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 1);
 
         assertFalse(queue.isRunnable());
         assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
 
         // Bumping past barrier makes us now runnable
-        airplaneRecord.terminalCount++;
+        airplaneRecord.setDeliveryState(0, BroadcastRecord.DELIVERY_DELIVERED,
+                "testRunnableAt_Ordered");
         queue.invalidateRunnableAt();
         assertTrue(queue.isRunnable());
         assertNotEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -480,7 +495,7 @@
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
                 List.of(makeMockRegisteredReceiver()));
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
 
         mConstants.MAX_PENDING_BROADCASTS = 128;
         queue.invalidateRunnableAt();
@@ -506,11 +521,11 @@
                 new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
                 List.of(makeMockRegisteredReceiver()));
 
-        queue.enqueueOrReplaceBroadcast(lazyRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, lazyRecord, 0);
         assertThat(queue.getRunnableAt()).isGreaterThan(lazyRecord.enqueueTime);
         assertThat(queue.getRunnableAtReason()).isNotEqualTo(testRunnableAtReason);
 
-        queue.enqueueOrReplaceBroadcast(testRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, testRecord, 0);
         assertThat(queue.getRunnableAt()).isAtMost(testRecord.enqueueTime);
         assertThat(queue.getRunnableAtReason()).isEqualTo(testRunnableAtReason);
     }
@@ -572,22 +587,22 @@
         BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
                 PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
 
-        queue.enqueueOrReplaceBroadcast(
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
-                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
-                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)
-                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
-                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false);
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
 
         queue.makeActiveNextPending();
         assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction());
@@ -1163,8 +1178,8 @@
 
         final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK)
                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick,
-                List.of(makeMockRegisteredReceiver())), 0, false);
+        enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(timeTick,
+                List.of(makeMockRegisteredReceiver())), 0);
         assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
 
         // Make the foreground broadcast as active.
@@ -1175,15 +1190,15 @@
         assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(airplane,
-                List.of(makeMockRegisteredReceiver())), 0, false);
+        enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(airplane,
+                List.of(makeMockRegisteredReceiver())), 0);
 
         // Make the background broadcast as active.
         queue.makeActiveNextPending();
         assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked());
 
-        queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick,
-                List.of(makeMockRegisteredReceiver())), 0, false);
+        enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(timeTick,
+                List.of(makeMockRegisteredReceiver())), 0);
         // Even though the active broadcast is not a foreground one, scheduling group will be
         // DEFAULT since there is a foreground broadcast waiting to be delivered.
         assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index b6bc02a..90e6a2d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
 import static android.os.UserHandle.USER_SYSTEM;
 
 import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
@@ -39,7 +41,6 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -463,7 +464,7 @@
 
         doAnswer((invocation) -> {
             Log.v(TAG, "Intercepting scheduleReceiver() for "
-                    + Arrays.toString(invocation.getArguments()));
+                    + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName);
             assertHealth();
             final Intent intent = invocation.getArgument(0);
             final Bundle extras = invocation.getArgument(5);
@@ -485,7 +486,7 @@
 
         doAnswer((invocation) -> {
             Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
-                    + Arrays.toString(invocation.getArguments()));
+                    + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName);
             assertHealth();
             final Intent intent = invocation.getArgument(1);
             final Bundle extras = invocation.getArgument(4);
@@ -961,7 +962,7 @@
             } else {
                 // Confirm that app was thawed
                 verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
-                        eq(receiverApp), eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
+                        eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
 
                 // Confirm that we added package to process
                 verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
@@ -1404,7 +1405,7 @@
 
         // Finally, verify that we thawed the final receiver
         verify(mAms.mOomAdjuster).unfreezeTemporarily(eq(callerApp),
-                eq(OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER));
+                eq(OOM_ADJ_REASON_FINISH_RECEIVER));
     }
 
     /**
@@ -1980,6 +1981,46 @@
     }
 
     /**
+     * Confirm how many times a pathological broadcast pattern results in OOM
+     * adjusts; watches for performance regressions.
+     */
+    @Test
+    public void testOomAdjust_TriggerCount() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+        // Send 8 broadcasts, 4 receivers in the first process,
+        // and 2 alternating in each of the remaining processes
+        synchronized (mAms) {
+            for (int i = 0; i < 8; i++) {
+                final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+                mQueue.enqueueBroadcastLocked(makeBroadcastRecord(intent, callerApp,
+                        List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                                makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                                makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                                makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                                makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+                                makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW),
+                                makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+                                makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+            }
+        }
+        waitForIdle();
+
+        final int expectedTimes;
+        switch (mImpl) {
+            // Original stack requested for every single receiver; yikes
+            case DEFAULT: expectedTimes = 64; break;
+            // Modern stack requests once each time we promote a process to
+            // running; we promote "green" twice, and "blue" and "yellow" once
+            case MODERN: expectedTimes = 4; break;
+            default: throw new UnsupportedOperationException();
+        }
+
+        verify(mAms, times(expectedTimes))
+                .updateOomAdjPendingTargetsLocked(eq(OOM_ADJ_REASON_START_RECEIVER));
+    }
+
+    /**
      * Verify that expected events are triggered when a broadcast is finished.
      */
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 2b6f217..08952ea 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -24,7 +24,12 @@
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount;
+import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING;
+import static com.android.server.am.BroadcastRecord.DELIVERY_SKIPPED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_TIMEOUT;
+import static com.android.server.am.BroadcastRecord.calculateBlockedUntilBeyondCount;
 import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive;
 import static com.android.server.am.BroadcastRecord.calculateUrgent;
 import static com.android.server.am.BroadcastRecord.isReceiverEquals;
@@ -58,7 +63,6 @@
 import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -79,6 +83,7 @@
 @SmallTest
 @RunWith(MockitoJUnitRunner.class)
 public class BroadcastRecordTest {
+    private static final String TAG = "BroadcastRecordTest";
 
     private static final int USER0 = UserHandle.USER_SYSTEM;
     private static final int USER1 = USER0 + 1;
@@ -120,13 +125,13 @@
         assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10))));
 
         assertArrayEquals(new int[] {-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 0)), false));
         assertArrayEquals(new int[] {-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), -10)), false));
         assertArrayEquals(new int[] {-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 10)), false));
     }
 
@@ -142,12 +147,12 @@
                 createResolveInfo(PACKAGE3, getAppId(3), 10))));
 
         assertArrayEquals(new int[] {-1,-1,-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 0),
                         createResolveInfo(PACKAGE2, getAppId(2), 0),
                         createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
         assertArrayEquals(new int[] {-1,-1,-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 10),
                         createResolveInfo(PACKAGE2, getAppId(2), 10),
                         createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
@@ -156,26 +161,176 @@
     @Test
     public void testIsPrioritized_Yes() {
         assertTrue(isPrioritized(List.of(
-                createResolveInfo(PACKAGE1, getAppId(1), -10),
+                createResolveInfo(PACKAGE1, getAppId(1), 10),
                 createResolveInfo(PACKAGE2, getAppId(2), 0),
-                createResolveInfo(PACKAGE3, getAppId(3), 10))));
+                createResolveInfo(PACKAGE3, getAppId(3), -10))));
         assertTrue(isPrioritized(List.of(
-                createResolveInfo(PACKAGE1, getAppId(1), 0),
+                createResolveInfo(PACKAGE1, getAppId(1), 10),
                 createResolveInfo(PACKAGE2, getAppId(2), 0),
-                createResolveInfo(PACKAGE3, getAppId(3), 10))));
+                createResolveInfo(PACKAGE3, getAppId(3), 0))));
 
         assertArrayEquals(new int[] {0,1,2},
-                calculateBlockedUntilTerminalCount(List.of(
-                        createResolveInfo(PACKAGE1, getAppId(1), -10),
+                calculateBlockedUntilBeyondCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 10),
                         createResolveInfo(PACKAGE2, getAppId(2), 0),
-                        createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
+                        createResolveInfo(PACKAGE3, getAppId(3), -10)), false));
         assertArrayEquals(new int[] {0,0,2,3,3},
-                calculateBlockedUntilTerminalCount(List.of(
-                        createResolveInfo(PACKAGE1, getAppId(1), 0),
-                        createResolveInfo(PACKAGE2, getAppId(2), 0),
+                calculateBlockedUntilBeyondCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 20),
+                        createResolveInfo(PACKAGE2, getAppId(2), 20),
                         createResolveInfo(PACKAGE3, getAppId(3), 10),
-                        createResolveInfo(PACKAGE3, getAppId(3), 20),
-                        createResolveInfo(PACKAGE3, getAppId(3), 20)), false));
+                        createResolveInfo(PACKAGE3, getAppId(3), 0),
+                        createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
+    }
+
+    @Test
+    public void testSetDeliveryState_Single() {
+        final BroadcastRecord r = createBroadcastRecord(
+                new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+                        createResolveInfoWithPriority(0)));
+        assertEquals(DELIVERY_PENDING, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+        r.setDeliveryState(0, DELIVERY_DEFERRED, TAG);
+        assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 0, 1, 1);
+
+        // Identical state change has no effect
+        r.setDeliveryState(0, DELIVERY_DEFERRED, TAG);
+        assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 0, 1, 1);
+
+        // Moving to terminal state updates counters
+        r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+        assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 1, 0, 1);
+
+        // Trying to change terminal state has no effect
+        r.setDeliveryState(0, DELIVERY_TIMEOUT, TAG);
+        assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 1, 0, 1);
+    }
+
+    @Test
+    public void testSetDeliveryState_Unordered() {
+        final BroadcastRecord r = createBroadcastRecord(
+                new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0)));
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+        // Even though we finish a middle item in the tranche, we're not
+        // "beyond" it because there is still unfinished work before it
+        r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 1, 0, 0);
+
+        r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 2, 0, 2);
+
+        r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 3, 0, 3);
+    }
+
+    @Test
+    public void testSetDeliveryState_Ordered() {
+        final BroadcastRecord r = createOrderedBroadcastRecord(
+                new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0)));
+        assertBlocked(r, false, true, true);
+        assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+        r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, true);
+        assertTerminalDeferredBeyond(r, 1, 0, 1);
+
+        r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 2, 0, 2);
+
+        r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 3, 0, 3);
+    }
+
+    @Test
+    public void testSetDeliveryState_DeferUntilActive() {
+        final BroadcastRecord r = createBroadcastRecord(
+                new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+                        createResolveInfoWithPriority(10),
+                        createResolveInfoWithPriority(10),
+                        createResolveInfoWithPriority(10),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(-10),
+                        createResolveInfoWithPriority(-10),
+                        createResolveInfoWithPriority(-10)));
+        assertBlocked(r, false, false, false, true, true, true, true, true, true);
+        assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+        r.setDeliveryState(0, DELIVERY_PENDING, TAG);
+        r.setDeliveryState(1, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(2, DELIVERY_PENDING, TAG);
+        r.setDeliveryState(3, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(4, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(5, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(6, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(7, DELIVERY_PENDING, TAG);
+        r.setDeliveryState(8, DELIVERY_DEFERRED, TAG);
+
+        // Verify deferred counts ratchet up, but we're not "beyond" the first
+        // still-pending receiver
+        assertBlocked(r, false, false, false, true, true, true, true, true, true);
+        assertTerminalDeferredBeyond(r, 0, 6, 0);
+
+        // We're still not "beyond" the first still-pending receiver, even when
+        // we finish a receiver later in the first tranche
+        r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false, true, true, true, true, true, true);
+        assertTerminalDeferredBeyond(r, 1, 6, 0);
+
+        // Completing that last item in first tranche means we now unblock the
+        // second tranche, and since it's entirely deferred, the third traunche
+        // is unblocked too
+        r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 2, 6, 7);
+
+        // Moving a deferred item in an earlier tranche back to being pending
+        // doesn't change the fact that we've already moved beyond it
+        r.setDeliveryState(1, DELIVERY_PENDING, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 2, 5, 7);
+        r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 3, 5, 7);
+
+        // Completing middle pending item is enough to fast-forward to end
+        r.setDeliveryState(7, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 4, 5, 9);
+
+        // Moving everyone else directly into a finished state updates all the
+        // terminal counters
+        r.setDeliveryState(3, DELIVERY_SKIPPED, TAG);
+        r.setDeliveryState(4, DELIVERY_SKIPPED, TAG);
+        r.setDeliveryState(5, DELIVERY_SKIPPED, TAG);
+        r.setDeliveryState(6, DELIVERY_SKIPPED, TAG);
+        r.setDeliveryState(8, DELIVERY_SKIPPED, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 9, 0, 9);
     }
 
     @Test
@@ -688,6 +843,10 @@
                 : errorMsg.insert(0, "Contains unexpected receiver: ").toString();
     }
 
+    private static ResolveInfo createResolveInfoWithPriority(int priority) {
+        return createResolveInfo(PACKAGE1, getAppId(1), priority);
+    }
+
     private static ResolveInfo createResolveInfo(String packageName, int uid) {
         return createResolveInfo(packageName, uid, 0);
     }
@@ -738,21 +897,40 @@
         return excludedList;
     }
 
+    private BroadcastRecord createBroadcastRecord(Intent intent,
+            List<ResolveInfo> receivers) {
+        return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */,
+                null /* options */, false);
+    }
+
+    private BroadcastRecord createOrderedBroadcastRecord(Intent intent,
+            List<ResolveInfo> receivers) {
+        return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */,
+                null /* options */, true);
+    }
+
     private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
             Intent intent) {
         return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
-                null /* options */);
+                null /* options */, false);
     }
 
     private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
             Intent intent, BroadcastOptions options) {
         return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
-                options);
+                options, false);
     }
 
     private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
             Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
             BroadcastOptions options) {
+        return createBroadcastRecord(receivers, userId, intent, filterExtrasForReceiver,
+                options, false);
+    }
+
+    private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
+            Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+            BroadcastOptions options, boolean ordered) {
         return new BroadcastRecord(
                 mQueue /* queue */,
                 intent,
@@ -774,7 +952,7 @@
                 0 /* resultCode */,
                 null /* resultData */,
                 null /* resultExtras */,
-                false /* serialized */,
+                ordered /* serialized */,
                 false /* sticky */,
                 false /* initialSticky */,
                 userId,
@@ -789,6 +967,20 @@
 
     private static boolean isPrioritized(List<Object> receivers) {
         return BroadcastRecord.isPrioritized(
-                calculateBlockedUntilTerminalCount(receivers, false), false);
+                calculateBlockedUntilBeyondCount(receivers, false), false);
+    }
+
+    private static void assertBlocked(BroadcastRecord r, boolean... blocked) {
+        assertEquals(r.receivers.size(), blocked.length);
+        for (int i = 0; i < blocked.length; i++) {
+            assertEquals("blocked " + i, blocked[i], r.isBlocked(i));
+        }
+    }
+
+    private static void assertTerminalDeferredBeyond(BroadcastRecord r,
+            int expectedTerminalCount, int expectedDeferredCount, int expectedBeyondCount) {
+        assertEquals("terminal", expectedTerminalCount, r.terminalCount);
+        assertEquals("deferred", expectedDeferredCount, r.deferredCount);
+        assertEquals("beyond", expectedBeyondCount, r.beyondCount);
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 485ce33..cda5456 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -38,11 +38,12 @@
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_ACTIVITY;
 import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
 import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
@@ -254,12 +255,13 @@
      * - If there's only one process, then it calls updateOomAdjLocked(ProcessRecord, int).
      * - Otherwise, sets the processes to the LRU and run updateOomAdjLocked(int).
      */
+    @SuppressWarnings("GuardedBy")
     private void updateOomAdj(ProcessRecord... apps) {
         if (apps.length == 1) {
-            sService.mOomAdjuster.updateOomAdjLocked(apps[0], OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
         } else {
             setProcessesToLru(apps);
-            sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
             sService.mProcessList.getLruProcessesLOSP().clear();
         }
     }
@@ -658,7 +660,7 @@
         ServiceRecord s = bindService(app, system,
                 null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
                 PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -1226,7 +1228,7 @@
                     mock(IBinder.class));
             client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
 
             assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
         }
@@ -1243,7 +1245,7 @@
                     mock(IBinder.class));
             client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
             doReturn(false).when(wpc).isHeavyWeightProcess();
 
             assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
@@ -1497,7 +1499,7 @@
 
         client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(client2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(client2, OOM_ADJ_REASON_NONE);
 
         assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState());
         assertEquals(PROCESS_STATE_CACHED_EMPTY, client.mState.getSetProcState());
@@ -1919,7 +1921,7 @@
         doReturn(client2).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
 
-        sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE);
         assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
     }
@@ -2029,7 +2031,7 @@
             setServiceMap(s3, MOCKAPP5_UID, cn3);
             setServiceMap(c2s, MOCKAPP3_UID, cn4);
             app2UidRecord.setIdle(false);
-            sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
             assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                     SCHED_GROUP_DEFAULT);
@@ -2055,7 +2057,7 @@
                     anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
             doNothing().when(sService.mServices)
                     .scheduleServiceTimeoutLocked(any(ProcessRecord.class));
-            sService.mOomAdjuster.updateOomAdjLocked(client1, OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE);
 
             assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState());
             assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState());
@@ -2427,7 +2429,7 @@
         app2.mState.setHasShownUi(false);
 
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services");
@@ -2436,7 +2438,7 @@
         app.mState.setAdjType(null);
         app.mState.setSetAdj(UNKNOWN_ADJ);
         app.mState.setHasShownUi(false);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
 
@@ -2445,7 +2447,7 @@
         app.mState.setAdjType(null);
         app.mState.setSetAdj(UNKNOWN_ADJ);
         s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
 
@@ -2463,7 +2465,7 @@
         s.lastActivity = now;
 
         app.mServices.startService(s);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2474,7 +2476,7 @@
         app.mState.setSetAdj(UNKNOWN_ADJ);
         app.mState.setHasShownUi(false);
         s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2482,7 +2484,7 @@
         doReturn(userOther).when(sService.mUserController).getCurrentUserId();
         sService.mOomAdjuster.handleUserSwitchedLocked();
 
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
         assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
index 201da35..9f685b4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
@@ -104,4 +104,24 @@
         assertThat(BackupAndRestoreFeatureFlags.getFullBackupUtilsRouteBufferSizeBytes())
                 .isEqualTo(5678);
     }
+
+    @Test
+    public void getUnifiedRestoreContinueAfterTransportFailureInKvRestore_notSet_returnsDefault() {
+        assertThat(
+                BackupAndRestoreFeatureFlags
+                        .getUnifiedRestoreContinueAfterTransportFailureInKvRestore())
+                .isEqualTo(true);
+    }
+
+    @Test
+    public void getUnifiedRestoreContinueAfterTransportFailureInKvRestore_set_returnsSetValue() {
+        DeviceConfig.setProperty(/*namespace=*/ "backup_and_restore",
+                /*name=*/ "unified_restore_continue_after_transport_failure_in_kv_restore",
+                /*value=*/ "false", /*makeDefault=*/ false);
+
+        assertThat(
+                BackupAndRestoreFeatureFlags
+                        .getUnifiedRestoreContinueAfterTransportFailureInKvRestore())
+                .isEqualTo(false);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
index 017c939..c84797f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -25,20 +25,33 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupTransport;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.Message;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import android.app.backup.BackupDataInput;
-import android.app.backup.BackupDataOutput;
-import android.platform.test.annotations.Presubmit;
-
+import com.android.modules.utils.testing.TestableDeviceConfig;
 import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.internal.BackupHandler;
+import com.android.server.backup.transport.BackupTransportClient;
+import com.android.server.backup.transport.TransportConnection;
+import com.android.server.backup.transport.TransportNotAvailableException;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
@@ -62,9 +75,14 @@
     private static final String SYSTEM_PACKAGE_NAME = "android";
     private static final String NON_SYSTEM_PACKAGE_NAME = "package";
 
-    @Mock private BackupDataInput mBackupDataInput;
-    @Mock private BackupDataOutput mBackupDataOutput;
-    @Mock private UserBackupManagerService mBackupManagerService;
+    @Mock
+    private BackupDataInput mBackupDataInput;
+    @Mock
+    private BackupDataOutput mBackupDataOutput;
+    @Mock
+    private UserBackupManagerService mBackupManagerService;
+    @Mock
+    private TransportConnection mTransportConnection;
 
     private Set<String> mExcludedkeys = new HashSet<>();
     private Map<String, String> mBackupData = new HashMap<>();
@@ -74,12 +92,20 @@
     private Set<String> mBackupDataDump;
     private PerformUnifiedRestoreTask mRestoreTask;
 
+    @Rule
+    public TestableDeviceConfig.TestableDeviceConfigRule
+            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+    private Context mContext;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         populateTestData();
 
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
         mBackupDataSource = new ArrayDeque<>(mBackupData.keySet());
         when(mBackupDataInput.readNextHeader()).then(new Answer<Boolean>() {
             @Override
@@ -106,7 +132,7 @@
                     }
                 });
 
-        mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService);
+        mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService, mTransportConnection);
     }
 
     private void populateTestData() {
@@ -179,4 +205,64 @@
 
         assertTrue(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME));
     }
+
+    @Test
+    public void testFailedKeyValueRestore_continueAfterFeatureEnabled_nextStateIsRunningQueue()
+            throws TransportNotAvailableException, RemoteException {
+        DeviceConfig.setProperty(
+                "backup_and_restore",
+                "unified_restore_continue_after_transport_failure_in_kv_restore",
+                "true",
+                false);
+
+        setupForRestoreKeyValueState(BackupTransport.TRANSPORT_ERROR);
+
+        mRestoreTask.setCurrentUnifiedRestoreStateForTesting(UnifiedRestoreState.RESTORE_KEYVALUE);
+        mRestoreTask.setStateDirForTesting(mContext.getCacheDir());
+
+        PackageInfo testPackageInfo = new PackageInfo();
+        testPackageInfo.packageName = "test.package.name";
+        mRestoreTask.initiateOneRestoreForTesting(testPackageInfo, 0L);
+        assertTrue(
+                mRestoreTask.getCurrentUnifiedRestoreStateForTesting()
+                        == UnifiedRestoreState.RUNNING_QUEUE);
+    }
+
+    @Test
+    public void testFailedKeyValueRestore_continueAfterFeatureDisabled_nextStateIsFinal()
+            throws RemoteException, TransportNotAvailableException {
+        DeviceConfig.setProperty(
+                "backup_and_restore",
+                "unified_restore_continue_after_transport_failure_in_kv_restore",
+                "false",
+                false);
+
+        setupForRestoreKeyValueState(BackupTransport.TRANSPORT_ERROR);
+
+        mRestoreTask.setCurrentUnifiedRestoreStateForTesting(UnifiedRestoreState.RESTORE_KEYVALUE);
+        mRestoreTask.setStateDirForTesting(mContext.getCacheDir());
+
+        PackageInfo testPackageInfo = new PackageInfo();
+        testPackageInfo.packageName = "test.package.name";
+        mRestoreTask.initiateOneRestoreForTesting(testPackageInfo, 0L);
+        assertTrue(
+                mRestoreTask.getCurrentUnifiedRestoreStateForTesting()
+                        == UnifiedRestoreState.FINAL);
+    }
+
+    private void setupForRestoreKeyValueState(int transportStatus)
+            throws RemoteException, TransportNotAvailableException {
+        // Mock BackupHandler to do nothing when executeNextState() is called
+        BackupHandler backupHandler = Mockito.mock(BackupHandler.class);
+        when(backupHandler.obtainMessage(anyInt(), any())).thenReturn(new Message());
+        when(backupHandler.sendMessage(any())).thenReturn(true);
+
+        // Return cache directory for any bookkeeping or maintaining persistent state.
+        when(mBackupManagerService.getDataDir()).thenReturn(mContext.getCacheDir());
+        when(mBackupManagerService.getBackupHandler()).thenReturn(backupHandler);
+
+        BackupTransportClient transport = Mockito.mock(BackupTransportClient.class);
+        when(transport.getRestoreData(any())).thenReturn(transportStatus);
+        when(mTransportConnection.connectOrThrow(any())).thenReturn(transport);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 45fefe4..ca857f1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -38,9 +38,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
-import android.content.ContentResolver;
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.res.Resources;
 import android.hardware.Sensor;
 import android.hardware.SensorEventListener;
@@ -51,18 +49,18 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.SystemProperties;
-import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.provider.Settings;
+import android.testing.TestableContext;
 import android.util.FloatProperty;
 import android.view.Display;
 import android.view.DisplayInfo;
 
-import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.display.RampAnimator.DualRampAnimator;
@@ -74,12 +72,12 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 import org.mockito.stubbing.Answer;
 
@@ -97,11 +95,9 @@
     private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
     private static final float PROX_SENSOR_MAX_RANGE = 5;
 
-    private MockitoSession mSession;
     private OffsettableClock mClock;
     private TestLooper mTestLooper;
     private Handler mHandler;
-    private Context mContextSpy;
     private DisplayPowerControllerHolder mHolder;
     private Sensor mProxSensor;
 
@@ -118,40 +114,44 @@
     @Mock
     private PowerManager mPowerManagerMock;
     @Mock
-    private Resources mResourcesMock;
-    @Mock
     private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
 
     @Captor
     private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
 
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule =
+            new ExtendedMockitoRule.Builder(this)
+                    .setStrictness(Strictness.LENIENT)
+                    .spyStatic(SystemProperties.class)
+                    .spyStatic(BatteryStatsService.class)
+                    .build();
+
     @Before
     public void setUp() throws Exception {
-        mSession = ExtendedMockito.mockitoSession()
-                .initMocks(this)
-                .strictness(Strictness.LENIENT)
-                .spyStatic(SystemProperties.class)
-                .spyStatic(LocalServices.class)
-                .spyStatic(BatteryStatsService.class)
-                .spyStatic(Settings.System.class)
-                .startMocking();
-        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mClock = new OffsettableClock.Stopped();
         mTestLooper = new TestLooper(mClock::now);
         mHandler = new Handler(mTestLooper.getLooper());
-        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
 
-        when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
-        when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+        // Put the system into manual brightness by default, just to minimize unexpected events and
+        // have a consistent starting state
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mCdsiMock);
+
+        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
 
         doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
                 SystemProperties.set(anyString(), any()));
-        doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
-                mCdsiMock).when(() -> LocalServices.getService(
-                ColorDisplayService.ColorDisplayServiceInternal.class));
         doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
-        doAnswer((Answer<Boolean>) invocationOnMock -> true).when(() ->
-                Settings.System.putFloatForUser(any(), any(), anyFloat(), anyInt()));
 
         setUpSensors();
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -159,8 +159,8 @@
 
     @After
     public void tearDown() {
-        mSession.finishMocking();
         LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
     }
 
     @Test
@@ -421,11 +421,9 @@
 
     @Test
     public void testDisplayBrightnessFollowers_AutomaticBrightness() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
         final float brightness = 0.4f;
         final float nits = 300;
         final float ambientLux = 3000;
@@ -436,7 +434,7 @@
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
         when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
         when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        DisplayPowerController followerDpc = mock(DisplayPowerController.class);
+        DisplayPowerController2 followerDpc = mock(DisplayPowerController2.class);
 
         mHolder.dpc.addDisplayBrightnessFollower(followerDpc);
         DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -542,11 +540,9 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_OFF;
@@ -577,17 +573,14 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        when(mResourcesMock.getBoolean(
-                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing))
-                .thenReturn(true);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
 
@@ -615,12 +608,7 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
-
+        // Tests are set up with manual brightness by default, so no need to set it here.
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_OFF;
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -632,11 +620,9 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -690,9 +676,9 @@
     public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
         float brightness = 0.3f;
         float nits = 500;
-        when(mResourcesMock.getBoolean(
-                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
-                .thenReturn(true);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
+                true);
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
 
@@ -753,7 +739,7 @@
                 any(HysteresisLevels.class),
                 any(HysteresisLevels.class),
                 any(HysteresisLevels.class),
-                eq(mContextSpy),
+                eq(mContext),
                 any(HighBrightnessModeController.class),
                 any(BrightnessThrottler.class),
                 isNull(),
@@ -862,7 +848,7 @@
         setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
 
         final DisplayPowerController2 dpc = new DisplayPowerController2(
-                mContextSpy, injector, mDisplayPowerCallbacksMock, mHandler,
+                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
                 mSensorManagerMock, mDisplayBlankerMock, display,
                 mBrightnessTrackerMock, brightnessSetting, () -> {},
                 hbmMetadata, /* bootCompleted= */ false);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index d9133a4..0b97c5c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -38,9 +38,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
-import android.content.ContentResolver;
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.res.Resources;
 import android.hardware.Sensor;
 import android.hardware.SensorEventListener;
@@ -51,18 +49,18 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.SystemProperties;
-import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.provider.Settings;
+import android.testing.TestableContext;
 import android.util.FloatProperty;
 import android.view.Display;
 import android.view.DisplayInfo;
 
-import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.display.RampAnimator.DualRampAnimator;
@@ -75,12 +73,12 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 import org.mockito.stubbing.Answer;
 
@@ -98,11 +96,9 @@
     private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
     private static final float PROX_SENSOR_MAX_RANGE = 5;
 
-    private MockitoSession mSession;
     private OffsettableClock mClock;
     private TestLooper mTestLooper;
     private Handler mHandler;
-    private Context mContextSpy;
     private DisplayPowerControllerHolder mHolder;
     private Sensor mProxSensor;
 
@@ -119,41 +115,45 @@
     @Mock
     private PowerManager mPowerManagerMock;
     @Mock
-    private Resources mResourcesMock;
-    @Mock
     private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
 
     @Captor
     private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
 
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule =
+            new ExtendedMockitoRule.Builder(this)
+                    .setStrictness(Strictness.LENIENT)
+                    .spyStatic(SystemProperties.class)
+                    .spyStatic(BatteryStatsService.class)
+                    .build();
+
     @Before
     public void setUp() throws Exception {
-        mSession = ExtendedMockito.mockitoSession()
-                .initMocks(this)
-                .strictness(Strictness.LENIENT)
-                .spyStatic(SystemProperties.class)
-                .spyStatic(LocalServices.class)
-                .spyStatic(BatteryStatsService.class)
-                .spyStatic(Settings.System.class)
-                .startMocking();
-        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mClock = new OffsettableClock.Stopped();
         mTestLooper = new TestLooper(mClock::now);
         mHandler = new Handler(mTestLooper.getLooper());
 
-        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+        // Put the system into manual brightness by default, just to minimize unexpected events and
+        // have a consistent starting state
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
 
-        when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
-        when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+
+        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mCdsiMock);
+
+        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
 
         doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
                 SystemProperties.set(anyString(), any()));
-        doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
-                mCdsiMock).when(() -> LocalServices.getService(
-                ColorDisplayService.ColorDisplayServiceInternal.class));
         doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
-        doAnswer((Answer<Boolean>) invocationOnMock -> true).when(() ->
-                Settings.System.putFloatForUser(any(), any(), anyFloat(), anyInt()));
 
         setUpSensors();
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -161,8 +161,8 @@
 
     @After
     public void tearDown() {
-        mSession.finishMocking();
         LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
     }
 
     @Test
@@ -425,11 +425,9 @@
 
     @Test
     public void testDisplayBrightnessFollowers_AutomaticBrightness() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
         final float brightness = 0.4f;
         final float nits = 300;
         final float ambientLux = 3000;
@@ -547,11 +545,9 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_OFF;
@@ -582,17 +578,14 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        when(mResourcesMock.getBoolean(
-                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing))
-                .thenReturn(true);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
 
@@ -620,12 +613,7 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
-
+        // Tests are set up with manual brightness by default, so no need to set it here.
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_OFF;
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -637,11 +625,10 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -695,9 +682,10 @@
     public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
         float brightness = 0.3f;
         float nits = 500;
-        when(mResourcesMock.getBoolean(
-                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
-                .thenReturn(true);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
+                true);
+
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
 
@@ -758,7 +746,7 @@
                 any(HysteresisLevels.class),
                 any(HysteresisLevels.class),
                 any(HysteresisLevels.class),
-                eq(mContextSpy),
+                eq(mContext),
                 any(HighBrightnessModeController.class),
                 any(BrightnessThrottler.class),
                 isNull(),
@@ -866,7 +854,7 @@
         setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
 
         final DisplayPowerController dpc = new DisplayPowerController(
-                mContextSpy, injector, mDisplayPowerCallbacksMock, mHandler,
+                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
                 mSensorManagerMock, mDisplayBlankerMock, display,
                 mBrightnessTrackerMock, brightnessSetting, () -> {},
                 hbmMetadata, /* bootCompleted= */ false);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java
new file mode 100644
index 0000000..e1fa8f52
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssAntennaInfoProviderTest {
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IBinder mBinder;
+    private GnssNative mGnssNative;
+
+    private GnssAntennaInfoProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssAntennaInfoProvider(mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testOnHalStarted() {
+        verify(mGnssNative, times(1)).startAntennaInfoListening();
+    }
+
+    @Test
+    public void testOnHalRestarted() {
+        mTestProvider.onHalRestarted();
+        verify(mGnssNative, times(2)).startAntennaInfoListening();
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
index fd9dfe8..bf96b1d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
@@ -74,7 +74,6 @@
     private @Mock Context mContext;
     private @Mock LocationManagerInternal mInternal;
     private @Mock GnssConfiguration mMockConfiguration;
-    private @Mock GnssNative.GeofenceCallbacks mGeofenceCallbacks;
     private @Mock IGnssMeasurementsListener mListener1;
     private @Mock IGnssMeasurementsListener mListener2;
     private @Mock IBinder mBinder1;
@@ -98,7 +97,6 @@
         Injector injector = new TestInjector(mContext);
         mGnssNative = spy(Objects.requireNonNull(
                 GnssNative.create(injector, mMockConfiguration)));
-        mGnssNative.setGeofenceCallbacks(mGeofenceCallbacks);
         mTestProvider = new GnssMeasurementsProvider(injector, mGnssNative);
         mGnssNative.register();
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java
new file mode 100644
index 0000000..64aa4b3
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssNavigationMessageListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+public class GnssNavigationMessageProviderTest {
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+            "mypackage", "attribution", "listener");
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IGnssNavigationMessageListener mListener;
+    private @Mock IBinder mBinder;
+
+    private GnssNative mGnssNative;
+
+    private GnssNavigationMessageProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mBinder).when(mListener).asBinder();
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssNavigationMessageProvider(injector, mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testAddListener() {
+        // add a request
+        mTestProvider.addListener(IDENTITY, mListener);
+        verify(mGnssNative, times(1)).startNavigationMessageCollection();
+
+        // remove a request
+        mTestProvider.removeListener(mListener);
+        verify(mGnssNative, times(1)).stopNavigationMessageCollection();
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java
new file mode 100644
index 0000000..49e5e69
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.location.gnss;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssNmeaListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssNmeaProviderTest {
+
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+            "mypackage", "attribution", "listener");
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IGnssNmeaListener mListener;
+    private @Mock IBinder mBinder;
+
+    private GnssNative mGnssNative;
+
+    private GnssNmeaProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mBinder).when(mListener).asBinder();
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssNmeaProvider(injector, mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testAddListener() {
+        // add a request
+        mTestProvider.addListener(IDENTITY, mListener);
+        verify(mGnssNative, times(1)).startNmeaMessageCollection();
+
+        // remove a request
+        mTestProvider.removeListener(mListener);
+        verify(mGnssNative, times(1)).stopNmeaMessageCollection();
+    }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java
new file mode 100644
index 0000000..ce2aec7f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.location.gnss;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssStatusListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssStatusProviderTest {
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+            "mypackage", "attribution", "listener");
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IGnssStatusListener mListener;
+    private @Mock IBinder mBinder;
+
+    private GnssNative mGnssNative;
+
+    private GnssStatusProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mBinder).when(mListener).asBinder();
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssStatusProvider(injector, mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testAddListener() {
+        // add a request
+        mTestProvider.addListener(IDENTITY, mListener);
+        verify(mGnssNative, times(1)).startSvStatusCollection();
+
+        // remove a request
+        mTestProvider.removeListener(mListener);
+        verify(mGnssNative, times(1)).stopSvStatusCollection();
+    }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
index b7ab6f80..2d962ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
@@ -562,6 +562,26 @@
     }
 
     @Override
+    protected boolean startSvStatusCollection() {
+        return true;
+    }
+
+    @Override
+    protected boolean stopSvStatusCollection() {
+        return true;
+    }
+
+    @Override
+    public boolean startNmeaMessageCollection() {
+        return true;
+    }
+
+    @Override
+    public boolean stopNmeaMessageCollection() {
+        return true;
+    }
+
+    @Override
     protected int getBatchSize() {
         return mBatchSize;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
new file mode 100644
index 0000000..541b077
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -0,0 +1,248 @@
+/*
+ * 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.rollback;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.VersionedPackage;
+import android.content.rollback.PackageRollbackInfo;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+import android.util.Log;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.PackageWatchdog;
+import com.android.server.SystemConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+import java.util.Scanner;
+
+
+@RunWith(AndroidJUnit4.class)
+public class RollbackPackageHealthObserverTest {
+    @Mock
+    private Context mMockContext;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private PackageWatchdog mMockPackageWatchdog;
+    @Mock
+    RollbackManager mRollbackManager;
+    @Mock
+    RollbackInfo mRollbackInfo;
+    @Mock
+    PackageRollbackInfo mPackageRollbackInfo;
+
+    private MockitoSession mSession;
+    private static final String APP_A = "com.package.a";
+    private static final long VERSION_CODE = 1L;
+    private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
+
+    private SystemConfig mSysConfig;
+
+    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    @Before
+    public void setup() {
+        mSysConfig = new SystemConfigTestClass();
+
+        mSession = ExtendedMockito.mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .spyStatic(PackageWatchdog.class)
+                .startMocking();
+
+        // Mock PackageWatchdog
+        doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
+                .when(() -> PackageWatchdog.getInstance(mMockContext));
+
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSession.finishMocking();
+    }
+
+    /**
+     * Subclass of SystemConfig without running the constructor.
+     */
+    private class SystemConfigTestClass extends SystemConfig {
+        SystemConfigTestClass() {
+            super(false);
+        }
+    }
+
+    @Test
+    public void testHealthCheckLevels() {
+        RollbackPackageHealthObserver observer =
+                spy(new RollbackPackageHealthObserver(mMockContext));
+        VersionedPackage testFailedPackage = new VersionedPackage(APP_A, VERSION_CODE);
+
+
+        when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+
+        // Crashes with no rollbacks available
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+                observer.onHealthCheckFailed(null,
+                        PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1));
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+                observer.onHealthCheckFailed(null,
+                        PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
+
+        // Make the rollbacks available
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo));
+        when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo));
+        when(mPackageRollbackInfo.getVersionRolledBackFrom()).thenReturn(testFailedPackage);
+
+        // native crash
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+                observer.onHealthCheckFailed(null,
+                        PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1));
+        // non-native crash
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+                observer.onHealthCheckFailed(testFailedPackage,
+                        PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
+        // Second non-native crash again
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+                observer.onHealthCheckFailed(testFailedPackage,
+                        PackageWatchdog.FAILURE_REASON_APP_CRASH, 2));
+        // Subsequent crashes when rollbacks have completed
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of());
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+                observer.onHealthCheckFailed(testFailedPackage,
+                        PackageWatchdog.FAILURE_REASON_APP_CRASH, 3));
+    }
+
+    /**
+     * Test that isAutomaticRollbackDenied works correctly when packages that are not
+     * denied are sent.
+     */
+    @Test
+    public void isRollbackAllowedTest_false() throws IOException {
+        final String contents =
+                "<config>\n"
+                + "    <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
+                + "</config>";
+        final File folder = createTempSubfolder("folder");
+        createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+
+        readPermissions(folder, /* Grant all permission flags */ ~0);
+
+        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+                new VersionedPackage("com.test.package", 1))).isEqualTo(false);
+    }
+
+    /**
+     * Test that isAutomaticRollbackDenied works correctly when packages that are
+     * denied are sent.
+     */
+    @Test
+    public void isRollbackAllowedTest_true() throws IOException {
+        final String contents =
+                "<config>\n"
+                + "    <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
+                + "</config>";
+        final File folder = createTempSubfolder("folder");
+        createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+
+        readPermissions(folder, /* Grant all permission flags */ ~0);
+
+        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+                new VersionedPackage("com.android.vending", 1))).isEqualTo(true);
+    }
+
+    /**
+     * Test that isAutomaticRollbackDenied works correctly when no config is present
+     */
+    @Test
+    public void isRollbackAllowedTest_noConfig() throws IOException {
+        final File folder = createTempSubfolder("folder");
+
+        readPermissions(folder, /* Grant all permission flags */ ~0);
+
+        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+                new VersionedPackage("com.android.vending", 1))).isEqualTo(false);
+    }
+
+    /**
+     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+     *
+     * @param folder   pre-existing subdirectory of mTemporaryFolder to put the file
+     * @param fileName name of the file (e.g. filename.xml) to create
+     * @param contents contents to write to the file
+     * @return the newly created file
+     */
+    private File createTempFile(File folder, String fileName, String contents)
+            throws IOException {
+        File file = new File(folder, fileName);
+        BufferedWriter bw = new BufferedWriter(new FileWriter(file));
+        bw.write(contents);
+        bw.close();
+
+        // Print to logcat for test debugging.
+        Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath());
+        Scanner input = new Scanner(file);
+        while (input.hasNextLine()) {
+            Log.d(LOG_TAG, input.nextLine());
+        }
+
+        return file;
+    }
+
+    private void readPermissions(File libraryDir, int permissionFlag) {
+        final XmlPullParser parser = Xml.newPullParser();
+        mSysConfig.readPermissions(parser, libraryDir, permissionFlag);
+    }
+
+    /**
+     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+     *
+     * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
+     * @return the folder
+     */
+    private File createTempSubfolder(String folderName)
+            throws IOException {
+        File folder = new File(mTemporaryFolder.getRoot(), folderName);
+        folder.mkdirs();
+        return folder;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index dbf5021..26a3ae1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -43,6 +43,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.ITrustManager;
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -52,6 +53,7 @@
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Binder;
@@ -83,6 +85,7 @@
     private static final long TEST_REQUEST_ID = 22;
 
     @Mock private Context mContext;
+    @Mock private Resources mResources;
     @Mock private BiometricContext mBiometricContext;
     @Mock private ITrustManager mTrustManager;
     @Mock private DevicePolicyManager mDevicePolicyManager;
@@ -104,6 +107,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
         when(mClientReceiver.asBinder()).thenReturn(mock(Binder.class));
         when(mBiometricContext.updateContext(any(), anyBoolean()))
                 .thenAnswer(invocation -> invocation.getArgument(0));
@@ -342,6 +346,33 @@
         testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null));
     }
 
+    @Test
+    public void testCallbackOnAcquired() throws RemoteException {
+        final String acquiredStr = "test_acquired_info_callback";
+        final String acquiredStrVendor = "test_acquired_info_callback_vendor";
+        setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
+
+        final AuthSession session = createAuthSession(mSensors,
+                false /* checkDevicePolicyManager */,
+                Authenticators.BIOMETRIC_STRONG,
+                TEST_REQUEST_ID,
+                0 /* operationId */,
+                0 /* userId */);
+
+        when(mContext.getString(com.android.internal.R.string.fingerprint_acquired_partial))
+            .thenReturn(acquiredStr);
+        session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL, 0);
+        verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStr));
+        verify(mClientReceiver).onAcquired(eq(1), eq(acquiredStr));
+
+        when(mResources.getStringArray(com.android.internal.R.array.fingerprint_acquired_vendor))
+            .thenReturn(new String[]{acquiredStrVendor});
+        session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 0);
+        verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStrVendor));
+        verify(mClientReceiver).onAcquired(
+                eq(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR_BASE), eq(acquiredStrVendor));
+    }
+
     // TODO (b/208484275) : Enable these tests
     // @Test
     // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 7468901..d5d06d3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -148,6 +150,28 @@
     }
 
     @Test
+    public void testLockoutEndsOperation() throws RemoteException {
+        final FaceAuthenticationClient client = createClient(2);
+        client.start(mCallback);
+        client.onLockoutPermanent();
+
+        verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+                eq(FACE_ERROR_LOCKOUT_PERMANENT), anyInt());
+        verify(mCallback).onClientFinished(client, false);
+    }
+
+    @Test
+    public void testTemporaryLockoutEndsOperation() throws RemoteException {
+        final FaceAuthenticationClient client = createClient(2);
+        client.start(mCallback);
+        client.onLockoutTimed(1000);
+
+        verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+                eq(FACE_ERROR_LOCKOUT), anyInt());
+        verify(mCallback).onClientFinished(client, false);
+    }
+
+    @Test
     public void notifyHalWhenContextChanges() throws RemoteException {
         final FaceAuthenticationClient client = createClient();
         client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 550204b..4cfbb95 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -105,6 +105,7 @@
         mMockPackageManager = mock(PackageManager.class);
         mMockPackageMonitor = mock(PackageMonitor.class);
 
+        doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
         // For unit tests, set the default installer info
         doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
                 .getInstallSourceInfo(anyString());
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index da9de25..e20f1e7 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -131,6 +131,7 @@
         doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
         doReturn(InstrumentationRegistry.getContext().getContentResolver())
                 .when(mMockContext).getContentResolver();
+        doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
 
         mStoragefile = new AtomicFile(new File(
                 Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml"));
diff --git a/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java b/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java
index 2230ddd..2bde51b 100644
--- a/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java
@@ -103,21 +103,45 @@
     }
 
     @Test
-    public void testCallerProcessBinderEntry() throws RemoteException {
+    public void testCallerProcessBinderEntries() throws RemoteException {
         List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfos = new ArrayList<>();
         runningAppProcessInfos.add(
                 new ActivityManager.RunningAppProcessInfo("process_name", 1, new String[0]));
         when(mActivityManager.getRunningAppProcesses()).thenReturn(runningAppProcessInfos);
 
         mTestInjector.setCurrentTime(1000);
+        // Matching pid in getRunningAppProcesses
+        mInstance.recordCheckPointInternal(1, "reason1");
+        // Mising pid in getRunningAppProcesses
+        mInstance.recordCheckPointInternal(2, "reason2");
+
+        assertEquals(
+                "Shutdown request from BINDER for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testCallerProcessBinderEntries\n"
+                        + "From process process_name (pid=1)\n\n"
+                        + "Shutdown request from BINDER for reason reason2 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testCallerProcessBinderEntries\n"
+                        + "From process ? (pid=2)\n\n",
+                dumpToString());
+    }
+
+    @Test
+    public void testNullCallerProcessBinderEntries() throws RemoteException {
+        when(mActivityManager.getRunningAppProcesses()).thenReturn(null);
+
+        mTestInjector.setCurrentTime(1000);
         mInstance.recordCheckPointInternal(1, "reason1");
 
         assertEquals(
                 "Shutdown request from BINDER for reason reason1 "
                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
                         + "com.android.server.power.ShutdownCheckPointsTest"
-                        + ".testCallerProcessBinderEntry\n"
-                        + "From process process_name (pid=1)\n\n",
+                        + ".testNullCallerProcessBinderEntries\n"
+                        + "From process ? (pid=1)\n\n",
                 dumpToString());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
deleted file mode 100644
index 0be678a..0000000
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
+++ /dev/null
@@ -1,157 +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.rollback;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.pm.VersionedPackage;
-import android.util.Log;
-import android.util.Xml;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.SystemConfig;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.xmlpull.v1.XmlPullParser;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Scanner;
-
-@RunWith(AndroidJUnit4.class)
-public class RollbackPackageHealthObserverTest {
-    private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
-
-    private SystemConfig mSysConfig;
-
-    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
-
-    @Before
-    public void setup() {
-        mSysConfig = new SystemConfigTestClass();
-    }
-
-    /**
-    * Subclass of SystemConfig without running the constructor.
-    */
-    private class SystemConfigTestClass extends SystemConfig {
-        SystemConfigTestClass() {
-          super(false);
-        }
-    }
-
-    /**
-     * Test that isAutomaticRollbackDenied works correctly when packages that are not
-     * denied are sent.
-     */
-    @Test
-    public void isRollbackAllowedTest_false() throws IOException {
-        final String contents =
-                "<config>\n"
-                + "    <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
-                + "</config>";
-        final File folder = createTempSubfolder("folder");
-        createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
-
-        readPermissions(folder, /* Grant all permission flags */ ~0);
-
-        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
-            new VersionedPackage("com.test.package", 1))).isEqualTo(false);
-    }
-
-    /**
-     * Test that isAutomaticRollbackDenied works correctly when packages that are
-     * denied are sent.
-     */
-    @Test
-    public void isRollbackAllowedTest_true() throws IOException {
-        final String contents =
-                "<config>\n"
-                + "    <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
-                + "</config>";
-        final File folder = createTempSubfolder("folder");
-        createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
-
-        readPermissions(folder, /* Grant all permission flags */ ~0);
-
-        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
-            new VersionedPackage("com.android.vending", 1))).isEqualTo(true);
-    }
-
-    /**
-     * Test that isAutomaticRollbackDenied works correctly when no config is present
-     */
-    @Test
-    public void isRollbackAllowedTest_noConfig() throws IOException {
-        final File folder = createTempSubfolder("folder");
-
-        readPermissions(folder, /* Grant all permission flags */ ~0);
-
-        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
-            new VersionedPackage("com.android.vending", 1))).isEqualTo(false);
-    }
-
-    /**
-     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
-     *
-     * @param folder   pre-existing subdirectory of mTemporaryFolder to put the file
-     * @param fileName name of the file (e.g. filename.xml) to create
-     * @param contents contents to write to the file
-     * @return the newly created file
-     */
-    private File createTempFile(File folder, String fileName, String contents)
-            throws IOException {
-        File file = new File(folder, fileName);
-        BufferedWriter bw = new BufferedWriter(new FileWriter(file));
-        bw.write(contents);
-        bw.close();
-
-        // Print to logcat for test debugging.
-        Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath());
-        Scanner input = new Scanner(file);
-        while (input.hasNextLine()) {
-            Log.d(LOG_TAG, input.nextLine());
-        }
-
-        return file;
-    }
-
-    private void readPermissions(File libraryDir, int permissionFlag) {
-        final XmlPullParser parser = Xml.newPullParser();
-        mSysConfig.readPermissions(parser, libraryDir, permissionFlag);
-    }
-
-    /**
-     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
-     *
-     * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
-     * @return the folder
-     */
-    private File createTempSubfolder(String folderName)
-            throws IOException {
-        File folder = new File(mTemporaryFolder.getRoot(), folderName);
-        folder.mkdirs();
-        return folder;
-    }
-}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index ff6c976..516fb4a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -15,23 +15,31 @@
  */
 package com.android.server.notification;
 
-import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.Notification.FLAG_CAN_COLORIZE;
+import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.Notification.FLAG_NO_CLEAR;
+import static android.app.Notification.FLAG_ONGOING_EVENT;
+
+import static com.android.server.notification.GroupHelper.BASE_FLAGS;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
 
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
+import android.annotation.SuppressLint;
 import android.app.Notification;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArrayMap;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -45,11 +53,10 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
-import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Map;
 
 @SmallTest
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class.
 @RunWith(AndroidJUnit4.class)
 public class GroupHelperTest extends UiServiceTestCase {
     private @Mock GroupHelper.Callback mCallback;
@@ -82,21 +89,104 @@
     }
 
     @Test
-    public void testNoGroup_postingUnderLimit() throws Exception {
+    public void testGetAutogroupSummaryFlags_noChildren() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+
+        assertEquals(BASE_FLAGS, mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_oneOngoing() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_ONGOING_EVENT);
+        children.put("c", FLAG_BUBBLE);
+
+        assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_oneOngoingNoClear() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_ONGOING_EVENT|FLAG_NO_CLEAR);
+        children.put("c", FLAG_BUBBLE);
+
+        assertEquals(FLAG_NO_CLEAR | FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_oneOngoingBubble() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_ONGOING_EVENT | FLAG_BUBBLE);
+        children.put("c", FLAG_BUBBLE);
+
+        assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_multipleOngoing() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_ONGOING_EVENT);
+        children.put("c", FLAG_BUBBLE);
+        children.put("d", FLAG_ONGOING_EVENT);
+
+        assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_oneAutoCancel() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_AUTO_CANCEL);
+        children.put("c", FLAG_BUBBLE);
+
+        assertEquals(BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_allAutoCancel() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", FLAG_AUTO_CANCEL);
+        children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
+        children.put("c", FLAG_AUTO_CANCEL);
+        children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE);
+
+        assertEquals(FLAG_AUTO_CANCEL | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_allAutoCancelOneOngoing() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", FLAG_AUTO_CANCEL);
+        children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
+        children.put("c", FLAG_AUTO_CANCEL);
+        children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT);
+
+        assertEquals(FLAG_AUTO_CANCEL| FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testNoGroup_postingUnderLimit() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
             mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
                     false);
         }
-        verify(mCallback, never()).addAutoGroupSummary(
-                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
-        verify(mCallback, never()).addAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verifyZeroInteractions(mCallback);
     }
 
     @Test
-    public void testNoGroup_multiPackage() throws Exception {
+    public void testNoGroup_multiPackage() {
         final String pkg = "package";
         final String pkg2 = "package2";
         for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
@@ -105,31 +195,23 @@
         }
         mGroupHelper.onNotificationPosted(
                 getSbn(pkg2, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM), false);
-        verify(mCallback, never()).addAutoGroupSummary(
-                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
-        verify(mCallback, never()).addAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verifyZeroInteractions(mCallback);
     }
 
     @Test
-    public void testNoGroup_multiUser() throws Exception {
+    public void testNoGroup_multiUser() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
             mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
                     false);
         }
         mGroupHelper.onNotificationPosted(
-                getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.ALL), false);
-        verify(mCallback, never()).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), anyBoolean());
-        verify(mCallback, never()).addAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+                getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.of(7)), false);
+        verifyZeroInteractions(mCallback);
     }
 
     @Test
-    public void testNoGroup_someAreGrouped() throws Exception {
+    public void testNoGroup_someAreGrouped() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
             mGroupHelper.onNotificationPosted(
@@ -137,233 +219,344 @@
         }
         mGroupHelper.onNotificationPosted(
                 getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM, "a"), false);
-        verify(mCallback, never()).addAutoGroupSummary(
-                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
-        verify(mCallback, never()).addAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verifyZeroInteractions(mCallback);
     }
 
     @Test
-    public void testPostingOverLimit() throws Exception {
+    public void testAddSummary() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
             mGroupHelper.onNotificationPosted(
                     getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testPostingOverLimit_addsOngoingFlag() throws Exception {
+    public void testAddSummary_oneChildOngoing_summaryOngoing() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
             StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
             if (i == 0) {
-                sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+                sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
             }
             mGroupHelper.onNotificationPosted(sbn, false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(true));
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_addingNoGroupSBN() {
+    public void testAddSummary_oneChildAutoCancel_summaryNotAutoCancel() {
         final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            }
+            mGroupHelper.onNotificationPosted(sbn, false);
         }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        verify(mCallback, times(AUTOGROUP_AT_COUNT + 1))
-            .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), AUTOGROUP_AT_COUNT + 1);
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_UpdateNotification() {
+    public void testAddSummary_allChildrenAutoCancel_summaryAutoCancel() {
         final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            mGroupHelper.onNotificationPosted(sbn, false);
         }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT;
-        mGroupHelper.onNotificationUpdated(notifications.get(0));
-
-        verify(mCallback, times(AUTOGROUP_AT_COUNT + 2))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), AUTOGROUP_AT_COUNT);
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_UpdateNotificationAfterChanges() {
+    public void testAddSummary_summaryAutoCancelNoClear() {
         final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_NO_CLEAR;
+            }
+            mGroupHelper.onNotificationPosted(sbn, false);
         }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT;
-
-        mGroupHelper.onNotificationUpdated(notifications.get(0));
-
-        notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-
-        mGroupHelper.onNotificationUpdated(notifications.get(0));
-
-        verify(mCallback, times(AUTOGROUP_AT_COUNT + 3))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), AUTOGROUP_AT_COUNT + 1);
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_RemoveNotification() {
+    public void testAutoGrouped_allOngoing_updateChildNotOngoing() {
         final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
         ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+            notifications.add(sbn);
         }
 
         for (StatusBarNotification sbn: notifications) {
-            sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // One notification is no longer ongoing
+        notifications.get(0).getNotification().flags &= ~FLAG_ONGOING_EVENT;
+        mGroupHelper.onNotificationPosted(notifications.get(0), true);
+
+        // Summary should keep FLAG_ONGOING_EVENT if any child has it
+        verify(mCallback).updateAutogroupSummary(
+                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+    }
+
+    @Test
+    public void testAutoGrouped_singleOngoing_removeOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+            }
+            notifications.add(sbn);
         }
 
         for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // remove ongoing
+        mGroupHelper.onNotificationRemoved(notifications.get(0));
+
+        // Summary is no longer ongoing
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+    }
+
+    @Test
+    public void testAutoGrouped_noOngoing_updateOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // update to ongoing
+        notifications.get(0).getNotification().flags |= FLAG_ONGOING_EVENT;
+        mGroupHelper.onNotificationPosted(notifications.get(0), true);
+
+        // Summary is now ongoing
+        verify(mCallback).updateAutogroupSummary(
+                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+    }
+
+    @Test
+    public void testAutoGrouped_noOngoing_addOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // add ongoing
+        StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT + 1, null, UserHandle.SYSTEM);
+        sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+        mGroupHelper.onNotificationPosted(sbn, true);
+
+        // Summary is now ongoing
+        verify(mCallback).updateAutogroupSummary(
+                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+    }
+
+    @Test
+    public void testAutoGrouped_singleOngoing_appGroupOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+            }
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // app group the ongoing child
+        StatusBarNotification sbn = getSbn(pkg, 0, "0", UserHandle.SYSTEM, "app group now");
+        mGroupHelper.onNotificationPosted(sbn, true);
+
+        // Summary is no longer ongoing
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+    }
+
+    @Test
+    public void testAutoGrouped_singleOngoing_removeNonOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+            }
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // remove ongoing
+        mGroupHelper.onNotificationRemoved(notifications.get(1));
+
+        // Summary is still ongoing
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+    }
+
+    @Test
+    public void testAutoGrouped_allAutoCancel_updateChildNotAutoCancel() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // One notification is no longer autocancelable
+        notifications.get(0).getNotification().flags &= ~FLAG_AUTO_CANCEL;
+        mGroupHelper.onNotificationPosted(notifications.get(0), true);
+
+        // Summary should no longer be autocancelable
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+    }
+
+    @Test
+    public void testAutoGrouped_almostAllAutoCancel_updateChildAutoCancel() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i != 0) {
+                sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            }
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // Missing notification is now autocancelable
+        notifications.get(0).getNotification().flags |= FLAG_AUTO_CANCEL;
+        mGroupHelper.onNotificationPosted(notifications.get(0), true);
+
+        // Summary should now autocancelable
+        verify(mCallback).updateAutogroupSummary(
+                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+    }
+
+    @Test
+    public void testAutoGrouped_allAutoCancel_updateChildAppGrouped() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // One notification is now grouped by app
+        StatusBarNotification sbn = getSbn(pkg, 0, "0", UserHandle.SYSTEM, "app group now");
+        mGroupHelper.onNotificationPosted(sbn, true);
+
+        // Summary should be still be autocancelable
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+    }
+
+    @Test
+    public void testAutoGrouped_allAutoCancel_removeChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
         }
 
         mGroupHelper.onNotificationRemoved(notifications.get(0));
 
-        verify(mCallback, times(AUTOGROUP_AT_COUNT + 2))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), AUTOGROUP_AT_COUNT);
-    }
-
-
-    @Test
-    public void testAutoGroupCount_UpdateToNoneOngoingNotification() {
-        final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-        mGroupHelper.onNotificationUpdated(notifications.get(0));
-
-        verify(mCallback, times(1))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), 1);
+        // Summary should still be autocancelable
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_AddOneOngoingNotification() {
-        final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
-        }
-        StatusBarNotification sbn = notifications.get(AUTOGROUP_AT_COUNT);
-        sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-        sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-
-
-        for (StatusBarNotification current: notifications) {
-            mGroupHelper.onNotificationPosted(current, true);
-        }
-
-        verify(mCallback, times(1))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), 1);
-    }
-
-    @Test
-    public void testAutoGroupCount_UpdateNoneOngoing() {
-        final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        verify(mCallback, times(0))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(userId, pkg), 0);
-    }
-
-
-    @Test
-    public void testDropToZeroRemoveGroup() throws Exception {
+    public void testDropToZeroRemoveGroup() {
         final String pkg = "package";
         List<StatusBarNotification> posted = new ArrayList<>();
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -371,7 +564,8 @@
             posted.add(sbn);
             mGroupHelper.onNotificationPosted(sbn, false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -390,7 +584,7 @@
     }
 
     @Test
-    public void testAppStartsGrouping() throws Exception {
+    public void testAppStartsGrouping() {
         final String pkg = "package";
         List<StatusBarNotification> posted = new ArrayList<>();
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -399,7 +593,7 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), anyBoolean());
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -408,9 +602,10 @@
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
             final StatusBarNotification sbn =
                     getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, "app group");
-            mGroupHelper.onNotificationPosted(sbn, false);
+            sbn.setOverrideGroupKey("autogrouped");
+            mGroupHelper.onNotificationPosted(sbn, true);
             verify(mCallback, times(1)).removeAutoGroup(sbn.getKey());
-            if (i < AUTOGROUP_AT_COUNT -1) {
+            if (i < AUTOGROUP_AT_COUNT - 1) {
                 verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
             }
         }
@@ -418,8 +613,7 @@
     }
 
     @Test
-    public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled()
-            throws Exception {
+    public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() {
         final String pkg = "package";
         List<StatusBarNotification> posted = new ArrayList<>();
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -427,7 +621,8 @@
             posted.add(sbn);
             mGroupHelper.onNotificationPosted(sbn, false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -441,10 +636,7 @@
         Mockito.reset(mCallback);
 
         // only one child remains
-        Map<String, LinkedHashSet<String>> ungroupedForUser =
-                mGroupHelper.mUngroupedNotifications.get(UserHandle.USER_SYSTEM);
-        assertNotNull(ungroupedForUser);
-        assertEquals(1, ungroupedForUser.get(pkg).size());
+        assertEquals(1, mGroupHelper.getNotGroupedByAppCount(UserHandle.USER_SYSTEM, pkg));
 
         // Add new notification; it should be autogrouped even though the total count is
         // < AUTOGROUP_AT_COUNT
@@ -454,5 +646,8 @@
         verify(mCallback, times(1)).addAutoGroup(sbn.getKey());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+        verify(mCallback, never()).addAutoGroupSummary(
+                anyInt(), anyString(), anyString(), 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 eceb589..9cfdaa7 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -754,13 +754,19 @@
 
     private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
             String groupKey, boolean isSummary) {
+        return generateNotificationRecord(channel, id, "tag" + System.currentTimeMillis(), groupKey,
+                isSummary);
+    }
+
+    private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
+            String tag, String groupKey, boolean isSummary) {
         Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
                 .setGroup(groupKey)
                 .setGroupSummary(isSummary);
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id,
-                "tag" + System.currentTimeMillis(), mUid, 0,
+                tag, mUid, 0,
                 nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
@@ -1899,7 +1905,8 @@
         mService.mSummaryByGroupKey.put("pkg", summary);
         mService.mAutobundledSummaries.put(0, new ArrayMap<>());
         mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
-        mService.updateAutobundledSummaryFlags(0, "pkg", true, false);
+        mService.updateAutobundledSummaryFlags(
+                0, "pkg", GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, false);
 
         assertTrue(summary.getSbn().isOngoing());
     }
@@ -1915,7 +1922,7 @@
         mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
         mService.mSummaryByGroupKey.put("pkg", summary);
 
-        mService.updateAutobundledSummaryFlags(0, "pkg", false, false);
+        mService.updateAutobundledSummaryFlags(0, "pkg", GroupHelper.BASE_FLAGS, false);
 
         assertFalse(summary.getSbn().isOngoing());
     }
@@ -2897,7 +2904,7 @@
         when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true);
 
         NotificationRecord r = mService.createAutoGroupSummary(
-                temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), false);
+                temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), 0);
 
         assertThat(r.isImportanceFixed()).isTrue();
     }
@@ -4213,7 +4220,7 @@
     }
 
     @Test
-    public void testOnlyAutogroupIfGroupChanged_noPriorNoti_autogroups() throws Exception {
+    public void testOnlyAutogroupIfNeeded_newNotification_ghUpdate() {
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
         mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
@@ -4226,17 +4233,18 @@
     }
 
     @Test
-    public void testOnlyAutogroupIfGroupChanged_groupChanged_autogroups()
-            throws Exception {
-        NotificationRecord r =
-                generateNotificationRecord(mTestNotificationChannel, 0, "group", false);
+    public void testOnlyAutogroupIfNeeded_groupChanged_ghUpdate() {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfNeeded_groupChanged_ghUpdate", "group", false);
         mService.addNotification(r);
 
-        r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
-        mService.addEnqueuedNotification(r);
+        NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfNeeded_groupChanged_ghUpdate", null, false);
+        mService.addEnqueuedNotification(update);
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                        r.getUid(), SystemClock.elapsedRealtime());
+                mService.new PostNotificationRunnable(update.getKey(),
+                        update.getSbn().getPackageName(), update.getUid(),
+                        SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -4244,16 +4252,39 @@
     }
 
     @Test
-    public void testOnlyAutogroupIfGroupChanged_noGroupChanged_autogroups()
-            throws Exception {
-        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, "group",
-                false);
+    public void testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate() {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate", "group", false);
         mService.addNotification(r);
-        mService.addEnqueuedNotification(r);
+
+        NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate", null, false);
+        update.getNotification().flags = FLAG_AUTO_CANCEL;
+        mService.addEnqueuedNotification(update);
+        NotificationManagerService.PostNotificationRunnable runnable =
+                mService.new PostNotificationRunnable(update.getKey(),
+                        update.getSbn().getPackageName(), update.getUid(),
+                        SystemClock.elapsedRealtime());
+        runnable.run();
+        waitForIdle();
+
+        verify(mGroupHelper, times(1)).onNotificationPosted(any(), anyBoolean());
+    }
+
+    @Test
+    public void testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate() {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate", null, false);
+        mService.addNotification(r);
+        NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate", null, false);
+        update.getNotification().color = Color.BLACK;
+        mService.addEnqueuedNotification(update);
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                        r.getUid(), SystemClock.elapsedRealtime());
+                mService.new PostNotificationRunnable(update.getKey(),
+                        update.getSbn().getPackageName(),
+                        update.getUid(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -10220,10 +10251,10 @@
 
         // grouphelper is a mock here, so make the calls it would make
 
-        // add summary; wait for it to be posted
-        mService.addAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(), nr1.getKey(),
-                true);
-        waitForIdle();
+        // add summary
+        mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
+                nr1.getSbn().getPackageName(), nr1.getKey(),
+                GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT));
 
         // cancel both children
         mBinderService.cancelNotificationWithTag(PKG, PKG, nr0.getSbn().getTag(),
@@ -10232,9 +10263,7 @@
                 nr1.getSbn().getId(), nr1.getSbn().getUserId());
         waitForIdle();
 
-        // group helper would send 'remove flag' and then 'remove summary' events
-        mService.updateAutobundledSummaryFlags(nr1.getUserId(), nr1.getSbn().getPackageName(),
-                false, false);
+        // group helper would send 'remove summary' event
         mService.clearAutogroupSummaryLocked(nr1.getUserId(), nr1.getSbn().getPackageName());
         waitForIdle();
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
index e6569f7..9fe0e49 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
@@ -98,6 +98,41 @@
     }
 
     @Test
+    public void testHasDiffs_autoBundled() {
+        NotificationRecord r = generateRecord();
+
+        NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
+                1,
+                r.getPackageVisibilityOverride(),
+                r.canShowBadge(),
+                r.canBubble(),
+                r.getNotification().isBubbleNotification(),
+                r.getChannel(),
+                r.getGroupKey(),
+                r.getPeopleOverride(),
+                r.getSnoozeCriteria(),
+                r.getUserSentiment(),
+                r.getSuppressedVisualEffects(),
+                r.getSystemGeneratedSmartActions(),
+                r.getSmartReplies(),
+                r.getImportance(),
+                r.getRankingScore(),
+                r.isConversation(),
+                r.getProposedImportance(),
+                r.hasSensitiveContent());
+
+        Bundle signals = new Bundle();
+        signals.putString(Adjustment.KEY_GROUP_KEY, "ranker_group");
+        Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0);
+        r.addAdjustment(adjustment);
+        NotificationAdjustmentExtractor adjustmentExtractor = new NotificationAdjustmentExtractor();
+        adjustmentExtractor.process(r);
+
+        assertTrue(extractorData.hasDiffForRankingLocked(r, 1));
+        assertTrue(extractorData.hasDiffForLoggingLocked(r, 1));
+    }
+
+    @Test
     public void testHasDiffs_sensitiveContentChange() {
         NotificationRecord r = generateRecord();
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
new file mode 100644
index 0000000..bcd807a
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -0,0 +1,319 @@
+/*
+ * 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.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.provider.Settings;
+import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeDiff;
+import android.service.notification.ZenPolicy;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArrayMap;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ZenModeDiffTest extends UiServiceTestCase {
+    // version is not included in the diff; manual & automatic rules have special handling
+    public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS =
+            Set.of("version", "manualRule", "automaticRules");
+
+    @Test
+    public void testRuleDiff_addRemoveSame() {
+        // Test add, remove, and both sides same
+        ZenModeConfig.ZenRule r = makeRule();
+
+        // Both sides same rule
+        ZenModeDiff.RuleDiff dSame = new ZenModeDiff.RuleDiff(r, r);
+        assertFalse(dSame.hasDiff());
+
+        // from existent rule to null: expect deleted
+        ZenModeDiff.RuleDiff deleted = new ZenModeDiff.RuleDiff(r, null);
+        assertTrue(deleted.hasDiff());
+        assertTrue(deleted.wasRemoved());
+
+        // from null to new rule: expect added
+        ZenModeDiff.RuleDiff added = new ZenModeDiff.RuleDiff(null, r);
+        assertTrue(added.hasDiff());
+        assertTrue(added.wasAdded());
+    }
+
+    @Test
+    public void testRuleDiff_fieldDiffs() throws Exception {
+        // Start these the same
+        ZenModeConfig.ZenRule r1 = makeRule();
+        ZenModeConfig.ZenRule r2 = makeRule();
+
+        // maps mapping field name -> expected output value as we set diffs
+        ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+        ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+        List<Field> fieldsForDiff = getFieldsForDiffCheck(
+                ZenModeConfig.ZenRule.class, Set.of()); // actually no exempt fields for ZenRule
+        generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
+
+        ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+        assertTrue(d.hasDiff());
+
+        // Now diff them and check that each of the fields has a diff
+        for (Field f : fieldsForDiff) {
+            String name = f.getName();
+            assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+            assertTrue(d.getDiffForField(name).hasDiff());
+            assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+            assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+            assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+            assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+        }
+    }
+
+    @Test
+    public void testConfigDiff_addRemoveSame() {
+        // Default config, will test add, remove, and no change
+        ZenModeConfig c = new ZenModeConfig();
+
+        ZenModeDiff.ConfigDiff dSame = new ZenModeDiff.ConfigDiff(c, c);
+        assertFalse(dSame.hasDiff());
+
+        ZenModeDiff.ConfigDiff added = new ZenModeDiff.ConfigDiff(null, c);
+        assertTrue(added.hasDiff());
+        assertTrue(added.wasAdded());
+
+        ZenModeDiff.ConfigDiff removed = new ZenModeDiff.ConfigDiff(c, null);
+        assertTrue(removed.hasDiff());
+        assertTrue(removed.wasRemoved());
+    }
+
+    @Test
+    public void testConfigDiff_fieldDiffs() throws Exception {
+        // these two start the same
+        ZenModeConfig c1 = new ZenModeConfig();
+        ZenModeConfig c2 = new ZenModeConfig();
+
+        // maps mapping field name -> expected output value as we set diffs
+        ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+        ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+        List<Field> fieldsForDiff = getFieldsForDiffCheck(
+                ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS);
+        generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
+
+        ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(d.hasDiff());
+
+        // Now diff them and check that each of the fields has a diff
+        for (Field f : fieldsForDiff) {
+            String name = f.getName();
+            assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+            assertTrue(d.getDiffForField(name).hasDiff());
+            assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+            assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+            assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+            assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+        }
+    }
+
+    @Test
+    public void testConfigDiff_specialSenders() {
+        // these two start the same
+        ZenModeConfig c1 = new ZenModeConfig();
+        ZenModeConfig c2 = new ZenModeConfig();
+
+        // set c1 and c2 to have some different senders
+        c1.allowMessagesFrom = ZenModeConfig.SOURCE_STAR;
+        c2.allowMessagesFrom = ZenModeConfig.SOURCE_CONTACT;
+        c1.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+        c2.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_NONE;
+
+        ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(d.hasDiff());
+
+        // Diff in top-level fields
+        assertTrue(d.getDiffForField("allowMessagesFrom").hasDiff());
+        assertTrue(d.getDiffForField("allowConversationsFrom").hasDiff());
+
+        // Bonus testing of stringification of people senders and conversation senders
+        assertTrue(d.toString().contains("allowMessagesFrom:stars->contacts"));
+        assertTrue(d.toString().contains("allowConversationsFrom:important->none"));
+    }
+
+    @Test
+    public void testConfigDiff_hasRuleDiffs() {
+        // two default configs
+        ZenModeConfig c1 = new ZenModeConfig();
+        ZenModeConfig c2 = new ZenModeConfig();
+
+        // two initially-identical rules
+        ZenModeConfig.ZenRule r1 = makeRule();
+        ZenModeConfig.ZenRule r2 = makeRule();
+
+        // one that will become a manual rule
+        ZenModeConfig.ZenRule m = makeRule();
+
+        // Add r1 to c1, but not r2 to c2 yet -- expect a rule to be deleted
+        c1.automaticRules.put(r1.id, r1);
+        ZenModeDiff.ConfigDiff deleteRule = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(deleteRule.hasDiff());
+        assertNotNull(deleteRule.getAllAutomaticRuleDiffs());
+        assertTrue(deleteRule.getAllAutomaticRuleDiffs().containsKey("ruleId"));
+        assertTrue(deleteRule.getAllAutomaticRuleDiffs().get("ruleId").wasRemoved());
+
+        // Change r2 a little, add r2 to c2 as an automatic rule and m as a manual rule
+        r2.component = null;
+        r2.pkg = "different";
+        c2.manualRule = m;
+        c2.automaticRules.put(r2.id, r2);
+
+        // Expect diffs in both manual rule (added) and automatic rule (changed)
+        ZenModeDiff.ConfigDiff changed = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(changed.hasDiff());
+        assertTrue(changed.getManualRuleDiff().hasDiff());
+
+        ArrayMap<String, ZenModeDiff.RuleDiff> automaticDiffs = changed.getAllAutomaticRuleDiffs();
+        assertNotNull(automaticDiffs);
+        assertTrue(automaticDiffs.containsKey("ruleId"));
+        assertNotNull(automaticDiffs.get("ruleId").getDiffForField("component"));
+        assertNull(automaticDiffs.get("ruleId").getDiffForField("component").to());
+        assertNotNull(automaticDiffs.get("ruleId").getDiffForField("pkg"));
+        assertEquals("different", automaticDiffs.get("ruleId").getDiffForField("pkg").to());
+    }
+
+    // Helper methods for working with configs, policies, rules
+    // Just makes a zen rule with fields filled in
+    private ZenModeConfig.ZenRule makeRule() {
+        ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+        rule.configurationActivity = new ComponentName("a", "a");
+        rule.component = new ComponentName("b", "b");
+        rule.conditionId = new Uri.Builder().scheme("hello").build();
+        rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE);
+        rule.enabled = true;
+        rule.creationTime = 123;
+        rule.id = "ruleId";
+        rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        rule.modified = false;
+        rule.name = "name";
+        rule.snoozing = true;
+        rule.pkg = "a";
+        return rule;
+    }
+
+    // Get the fields on which we would want to check a diff. The requirements are: not final or/
+    // static (as these should/can never change), and not in a specific list that's exempted.
+    private List<Field> getFieldsForDiffCheck(Class c, Set<String> exemptNames)
+            throws SecurityException {
+        Field[] fields = c.getDeclaredFields();
+        ArrayList<Field> out = new ArrayList<>();
+
+        for (Field field : fields) {
+            // Check for exempt reasons
+            int m = field.getModifiers();
+            if (Modifier.isFinal(m)
+                    || Modifier.isStatic(m)
+                    || exemptNames.contains(field.getName())) {
+                continue;
+            }
+            out.add(field);
+        }
+        return out;
+    }
+
+    // Generate a set of generic diffs for the specified two objects and the fields to generate
+    // diffs for, and store the results in the provided expectation maps to be able to check the
+    // output later.
+    private void generateFieldDiffs(Object a, Object b, List<Field> fields,
+            ArrayMap<String, Object> expectedA, ArrayMap<String, Object> expectedB)
+            throws Exception {
+        // different classes passed in means bad input
+        assertEquals(a.getClass(), b.getClass());
+
+        // Loop through fields for which we want to check diffs, set a diff and keep track of
+        // what we set.
+        for (Field f : fields) {
+            f.setAccessible(true);
+            // Just double-check also that the fields actually are for the class declared
+            assertEquals(f.getDeclaringClass(), a.getClass());
+            Class t = f.getType();
+            // handle the full set of primitive types first
+            if (boolean.class.equals(t)) {
+                f.setBoolean(a, true);
+                expectedA.put(f.getName(), true);
+                f.setBoolean(b, false);
+                expectedB.put(f.getName(), false);
+            } else if (int.class.equals(t)) {
+                // these are not actually valid going to be valid for arbitrary int enum fields, but
+                // we just put something in there regardless.
+                f.setInt(a, 2);
+                expectedA.put(f.getName(), 2);
+                f.setInt(b, 1);
+                expectedB.put(f.getName(), 1);
+            } else if (long.class.equals(t)) {
+                f.setLong(a, 200L);
+                expectedA.put(f.getName(), 200L);
+                f.setLong(b, 100L);
+                expectedB.put(f.getName(), 100L);
+            } else if (t.isPrimitive()) {
+                // This method doesn't yet handle other primitive types. If the relevant diff
+                // classes gain new fields of these types, please add another clause here.
+                fail("primitive type not handled by generateFieldDiffs: " + t.getName());
+            } else if (String.class.equals(t)) {
+                f.set(a, "string1");
+                expectedA.put(f.getName(), "string1");
+                f.set(b, "string2");
+                expectedB.put(f.getName(), "string2");
+            } else {
+                // catch-all for other types: have the field be "added"
+                f.set(a, null);
+                expectedA.put(f.getName(), null);
+                try {
+                    f.set(b, t.getDeclaredConstructor().newInstance());
+                    expectedB.put(f.getName(), t.getDeclaredConstructor().newInstance());
+                } catch (Exception e) {
+                    // No default constructor, or blithely attempting to construct something doesn't
+                    // work for some reason. If the default value isn't null, then keep it.
+                    if (f.get(b) != null) {
+                        expectedB.put(f.getName(), f.get(b));
+                    } else {
+                        // If we can't even rely on that, fail. Have the test-writer special case
+                        // something, as this is not able to be genericized.
+                        fail("could not generically construct value for field: " + f.getName());
+                    }
+                }
+            }
+        }
+    }
+}
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 6f9798e..b2a5401 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -69,8 +69,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.AutomaticZenRule;
@@ -78,7 +76,6 @@
 import android.app.NotificationManager.Policy;
 import android.content.ComponentName;
 import android.content.ContentResolver;
-import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -90,7 +87,6 @@
 import android.media.AudioSystem;
 import android.media.VolumePolicy;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Process;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -98,6 +94,7 @@
 import android.service.notification.Condition;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeDiff;
 import android.service.notification.ZenPolicy;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -877,7 +874,8 @@
         mZenModeHelperSpy.readXml(parser, false, UserHandle.USER_ALL);
 
         assertEquals("Config mismatch: current vs expected: "
-                + mZenModeHelperSpy.mConfig.diff(expected), expected, mZenModeHelperSpy.mConfig);
+                + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, expected), expected,
+                mZenModeHelperSpy.mConfig);
     }
 
     @Test
@@ -1046,7 +1044,8 @@
 
         ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10);
         assertEquals(
-                "Config mismatch: current vs expected: " + actual.diff(config10), config10, actual);
+                "Config mismatch: current vs expected: "
+                        + new ZenModeDiff.ConfigDiff(actual, config10), config10, actual);
         assertNotEquals("Expected config mismatch", config11, mZenModeHelperSpy.mConfigs.get(11));
     }
 
@@ -1062,7 +1061,8 @@
         mZenModeHelperSpy.readXml(parser, true, UserHandle.USER_SYSTEM);
 
         assertEquals("Config mismatch: current vs original: "
-                + mZenModeHelperSpy.mConfig.diff(original), original, mZenModeHelperSpy.mConfig);
+                + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, original),
+                original, mZenModeHelperSpy.mConfig);
         assertEquals(original.hashCode(), mZenModeHelperSpy.mConfig.hashCode());
     }
 
@@ -1083,8 +1083,9 @@
 
         ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10);
         expected.user = 10;
-        assertEquals(
-                "Config mismatch: current vs original: " + actual.diff(expected), expected, actual);
+        assertEquals("Config mismatch: current vs original: "
+                        + new ZenModeDiff.ConfigDiff(actual, expected),
+                expected, actual);
         assertEquals(expected.hashCode(), actual.hashCode());
         expected.user = 0;
         assertNotEquals(expected, mZenModeHelperSpy.mConfig);
diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp
index 986fb71..e704ebf 100644
--- a/services/tests/voiceinteractiontests/Android.bp
+++ b/services/tests/voiceinteractiontests/Android.bp
@@ -40,6 +40,7 @@
         "platform-test-annotations",
         "services.core",
         "services.voiceinteraction",
+        "services.soundtrigger",
         "servicestests-core-utils",
         "servicestests-utils-mockito-extended",
         "truth-prebuilt",
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 341b331..8f2b470 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -83,6 +83,7 @@
 import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED;
 import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED;
 import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REQUESTED;
+import static com.android.server.wm.ActivityRecord.LAUNCH_SOURCE_TYPE_HOME;
 import static com.android.server.wm.ActivityRecord.State.DESTROYED;
 import static com.android.server.wm.ActivityRecord.State.DESTROYING;
 import static com.android.server.wm.ActivityRecord.State.FINISHING;
@@ -3688,6 +3689,23 @@
         assertTrue(activity.inTransition());
     }
 
+    /**
+     * Verifies the task is moved to back when back pressed if the root activity was originally
+     * started from Launcher.
+     */
+    @Test
+    public void testMoveTaskToBackWhenStartedFromLauncher() {
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord ar = createActivityRecord(task);
+        task.realActivity = ar.mActivityComponent;
+        ar.intent.setAction(Intent.ACTION_MAIN);
+        ar.intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        doReturn(true).when(ar).isLaunchSourceType(eq(LAUNCH_SOURCE_TYPE_HOME));
+
+        mAtm.mActivityClientController.onBackPressed(ar.token, null /* callback */);
+        verify(task).moveTaskToBack(any());
+    }
+
     private ICompatCameraControlCallback getCompatCameraControlCallback() {
         return new ICompatCameraControlCallback.Stub() {
             @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index c131c84..7092b0b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -104,7 +104,6 @@
         when(mMockRunner.asBinder()).thenReturn(new Binder());
         mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
                 DEFAULT_DISPLAY));
-        mController.mShouldAttachNavBarToAppDuringTransition = false;
         mRootHomeTask = mDefaultDisplay.getDefaultTaskDisplayArea().getRootHomeTask();
         assertNotNull(mRootHomeTask);
     }
@@ -814,13 +813,13 @@
     }
 
     private void setupForShouldAttachNavBarDuringTransition() {
-        mController.mShouldAttachNavBarToAppDuringTransition = true;
         final WindowState navBar = spy(createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar"));
         mDefaultDisplay.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
         mWm.setRecentsAnimationController(mController);
         doReturn(navBar).when(mController).getNavigationBarWindow();
         final DisplayPolicy displayPolicy = spy(mDefaultDisplay.getDisplayPolicy());
         doReturn(displayPolicy).when(mDefaultDisplay).getDisplayPolicy();
+        doReturn(true).when(displayPolicy).shouldAttachNavBarToAppDuringTransition();
     }
 
     private static void initializeRecentsAnimationController(RecentsAnimationController controller,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 90506d4..43b429c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1404,19 +1404,17 @@
         // We are now going to simulate closing task1 to return back to (open) task2.
         final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE);
 
-        closeTransition.collectExistenceChange(task1);
-        closeTransition.collectExistenceChange(activity1);
         closeTransition.collectExistenceChange(task2);
         closeTransition.collectExistenceChange(activity2);
         closeTransition.setTransientLaunch(activity2, task1);
         final Transition.ChangeInfo task1ChangeInfo = closeTransition.mChanges.get(task1);
         assertNotNull(task1ChangeInfo);
         assertTrue(task1ChangeInfo.hasChanged());
+        // Make sure the unrelated activity is NOT collected.
         final Transition.ChangeInfo activity1ChangeInfo = closeTransition.mChanges.get(activity1);
-        assertNotNull(activity1ChangeInfo);
-        assertTrue(activity1ChangeInfo.hasChanged());
+        assertNull(activity1ChangeInfo);
         // No need to wait for the activity in transient hide task.
-        assertTrue(activity1.isSyncFinished());
+        assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState);
 
         activity1.setVisibleRequested(false);
         activity2.setVisibleRequested(true);
@@ -1444,6 +1442,7 @@
                 }
             }
         });
+        assertTrue(activity1.isVisible());
         controller.finishTransition(closeTransition);
         assertTrue(wasInFinishingTransition[0]);
         assertNull(controller.mFinishingTransition);
@@ -1452,6 +1451,7 @@
         assertEquals(ActivityTaskManagerService.APP_SWITCH_DISALLOW, mAtm.getBalAppSwitchesState());
         // Because task1 is occluded by task2, finishTransition should make activity1 invisible.
         assertFalse(activity1.isVisibleRequested());
+        // Make sure activity1 visibility was committed
         assertFalse(activity1.isVisible());
         assertFalse(activity1.app.hasActivityInVisibleTask());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 6cf2b2d..74ba45c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -500,7 +500,6 @@
         RecentsAnimationController controller = new RecentsAnimationController(
                 mWm, mockRunner, null, displayId);
         spyOn(controller);
-        controller.mShouldAttachNavBarToAppDuringTransition = true;
         doReturn(mNavBarWindow).when(controller).getNavigationBarWindow();
         mWm.setRecentsAnimationController(controller);
 
@@ -508,6 +507,10 @@
         spyOn(mDisplayContent.mInputMethodWindow);
         doReturn(true).when(mDisplayContent.mInputMethodWindow).isVisible();
 
+        DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+        spyOn(policy);
+        doReturn(true).when(policy).shouldAttachNavBarToAppDuringTransition();
+
         // create home activity
         Task rootHomeTask = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
         final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
index 337e1f9..7fe8582 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
@@ -27,6 +27,8 @@
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.audio.AudioService;
 
+import java.util.Arrays;
+
 /**
  * Represents the ALSA specification, and attributes of an ALSA device.
  */
@@ -36,17 +38,21 @@
 
     private final int mCardNum;
     private final int mDeviceNum;
+    private final String mAlsaCardDeviceString;
     private final String mDeviceAddress;
-    private final boolean mHasOutput;
-    private final boolean mHasInput;
 
-    private final boolean mIsInputHeadset;
-    private final boolean mIsOutputHeadset;
+    // The following two constant will be used as index to access arrays.
+    private static final int INPUT = 0;
+    private static final int OUTPUT = 1;
+    private static final int NUM_DIRECTIONS = 2;
+    private static final String[] DIRECTION_STR = {"INPUT", "OUTPUT"};
+    private final boolean[] mHasDevice = new boolean[NUM_DIRECTIONS];
+
+    private final boolean[] mIsHeadset = new boolean[NUM_DIRECTIONS];
     private final boolean mIsDock;
-
-    private boolean mSelected = false;
-    private int mOutputState;
-    private int mInputState;
+    private final int[] mDeviceType = new int[NUM_DIRECTIONS];
+    private boolean[] mIsSelected = new boolean[NUM_DIRECTIONS];
+    private int[] mState = new int[NUM_DIRECTIONS];
     private UsbAlsaJackDetector mJackDetector;
     private IAudioService mAudioService;
 
@@ -60,11 +66,13 @@
         mCardNum = card;
         mDeviceNum = device;
         mDeviceAddress = deviceAddress;
-        mHasOutput = hasOutput;
-        mHasInput = hasInput;
-        mIsInputHeadset = isInputHeadset;
-        mIsOutputHeadset = isOutputHeadset;
+        mHasDevice[OUTPUT] = hasOutput;
+        mHasDevice[INPUT] = hasInput;
+        mIsHeadset[INPUT] = isInputHeadset;
+        mIsHeadset[OUTPUT] = isOutputHeadset;
         mIsDock = isDock;
+        initDeviceType();
+        mAlsaCardDeviceString = getAlsaCardDeviceString();
     }
 
     /**
@@ -104,28 +112,28 @@
      * @return true if the device supports output.
      */
     public boolean hasOutput() {
-        return mHasOutput;
+        return mHasDevice[OUTPUT];
     }
 
     /**
      * @return true if the device supports input (recording).
      */
     public boolean hasInput() {
-        return mHasInput;
-    }
-
-    /**
-     * @return true if the device is a headset for purposes of input.
-     */
-    public boolean isInputHeadset() {
-        return mIsInputHeadset;
+        return mHasDevice[INPUT];
     }
 
     /**
      * @return true if the device is a headset for purposes of output.
      */
     public boolean isOutputHeadset() {
-        return mIsOutputHeadset;
+        return mIsHeadset[OUTPUT];
+    }
+
+    /**
+     * @return true if the device is a headset for purposes of input.
+     */
+    public boolean isInputHeadset() {
+        return mIsHeadset[INPUT];
     }
 
     /**
@@ -157,6 +165,9 @@
 
     /** Begins a jack-detection thread. */
     private synchronized void startJackDetect() {
+        if (mJackDetector != null) {
+            return;
+        }
         // If no jack detect capabilities exist, mJackDetector will be null.
         mJackDetector = UsbAlsaJackDetector.startJackDetect(this);
     }
@@ -171,75 +182,152 @@
 
     /** Start using this device as the selected USB Audio Device. */
     public synchronized void start() {
-        mSelected = true;
-        mInputState = 0;
-        mOutputState = 0;
+        startInput();
+        startOutput();
+    }
+
+    /** Start using this device as the selected USB input device. */
+    public synchronized void startInput() {
+        startDevice(INPUT);
+    }
+
+    /** Start using this device as selected USB output device. */
+    public synchronized void startOutput() {
+        startDevice(OUTPUT);
+    }
+
+    private void startDevice(int direction) {
+        if (mIsSelected[direction]) {
+            return;
+        }
+        mIsSelected[direction] = true;
+        mState[direction] = 0;
         startJackDetect();
-        updateWiredDeviceConnectionState(true);
+        updateWiredDeviceConnectionState(direction, true /*enable*/);
     }
 
     /** Stop using this device as the selected USB Audio Device. */
     public synchronized void stop() {
-        stopJackDetect();
-        updateWiredDeviceConnectionState(false);
-        mSelected = false;
+        stopInput();
+        stopOutput();
     }
 
-    /** Updates AudioService with the connection state of the alsaDevice.
-     *  Checks ALSA Jack state for inputs and outputs before reporting.
+    /** Stop using this device as the selected USB input device. */
+    public synchronized void stopInput() {
+        if (!mIsSelected[INPUT]) {
+            return;
+        }
+        if (!mIsSelected[OUTPUT]) {
+            // Stop jack detection when both input and output are stopped
+            stopJackDetect();
+        }
+        updateInputWiredDeviceConnectionState(false /*enable*/);
+        mIsSelected[INPUT] = false;
+    }
+
+    /** Stop using this device as the selected USB output device. */
+    public synchronized void stopOutput() {
+        if (!mIsSelected[OUTPUT]) {
+            return;
+        }
+        if (!mIsSelected[INPUT]) {
+            // Stop jack detection when both input and output are stopped
+            stopJackDetect();
+        }
+        updateOutputWiredDeviceConnectionState(false /*enable*/);
+        mIsSelected[OUTPUT] = false;
+    }
+
+    private void initDeviceType() {
+        mDeviceType[INPUT] = mHasDevice[INPUT]
+                ? (mIsHeadset[INPUT] ? AudioSystem.DEVICE_IN_USB_HEADSET
+                                     : AudioSystem.DEVICE_IN_USB_DEVICE)
+                : AudioSystem.DEVICE_NONE;
+        mDeviceType[OUTPUT] = mHasDevice[OUTPUT]
+                ? (mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
+                           : (mIsHeadset[OUTPUT] ? AudioSystem.DEVICE_OUT_USB_HEADSET
+                                                 : AudioSystem.DEVICE_OUT_USB_DEVICE))
+                : AudioSystem.DEVICE_NONE;
+    }
+
+    /**
+     * @return the output device type that will be used to notify AudioService about device
+     *         connection. If there is no output on this device, {@link AudioSystem#DEVICE_NONE}
+     *         will be returned.
      */
-    public synchronized void updateWiredDeviceConnectionState(boolean enable) {
-        if (!mSelected) {
-            Slog.e(TAG, "updateWiredDeviceConnectionState on unselected AlsaDevice!");
-            return;
-        }
-        String alsaCardDeviceString = getAlsaCardDeviceString();
-        if (alsaCardDeviceString == null) {
-            return;
-        }
-        try {
-            // Output Device
-            if (mHasOutput) {
-                int device = mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
-                        : (mIsOutputHeadset
-                            ? AudioSystem.DEVICE_OUT_USB_HEADSET
-                            : AudioSystem.DEVICE_OUT_USB_DEVICE);
-                if (DEBUG) {
-                    Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device)
-                            + " addr:" + alsaCardDeviceString
-                            + " name:" + mDeviceName);
-                }
-                boolean connected = isOutputJackConnected();
-                Slog.i(TAG, "OUTPUT JACK connected: " + connected);
-                int outputState = (enable && connected) ? 1 : 0;
-                if (outputState != mOutputState) {
-                    mOutputState = outputState;
-                    AudioDeviceAttributes attributes = new AudioDeviceAttributes(device,
-                            alsaCardDeviceString, mDeviceName);
-                    mAudioService.setWiredDeviceConnectionState(attributes, outputState, TAG);
-                }
-            }
-
-            // Input Device
-            if (mHasInput) {
-                int device = mIsInputHeadset
-                        ? AudioSystem.DEVICE_IN_USB_HEADSET
-                        : AudioSystem.DEVICE_IN_USB_DEVICE;
-                boolean connected = isInputJackConnected();
-                Slog.i(TAG, "INPUT JACK connected: " + connected);
-                int inputState = (enable && connected) ? 1 : 0;
-                if (inputState != mInputState) {
-                    mInputState = inputState;
-                    AudioDeviceAttributes attributes = new AudioDeviceAttributes(device,
-                            alsaCardDeviceString, mDeviceName);
-                    mAudioService.setWiredDeviceConnectionState(attributes, inputState, TAG);
-                }
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
-        }
+    public int getOutputDeviceType() {
+        return mDeviceType[OUTPUT];
     }
 
+    /**
+     * @return the input device type that will be used to notify AudioService about device
+     *         connection. If there is no input on this device, {@link AudioSystem#DEVICE_NONE}
+     *         will be returned.
+     */
+    public int getInputDeviceType() {
+        return mDeviceType[INPUT];
+    }
+
+    private boolean updateWiredDeviceConnectionState(int direction, boolean enable) {
+        if (!mIsSelected[direction]) {
+            Slog.e(TAG, "Updating wired device connection state on unselected device");
+            return false;
+        }
+        if (mDeviceType[direction] == AudioSystem.DEVICE_NONE) {
+            Slog.d(TAG,
+                    "Unable to set device connection state as " + DIRECTION_STR[direction]
+                    + " device type is none");
+            return false;
+        }
+        if (mAlsaCardDeviceString == null) {
+            Slog.w(TAG, "Failed to update " + DIRECTION_STR[direction] + " device connection "
+                    + "state failed as alsa card device string is null");
+            return false;
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(mDeviceType[direction])
+                    + " addr:" + mAlsaCardDeviceString
+                    + " name:" + mDeviceName);
+        }
+        boolean connected = direction == INPUT ? isInputJackConnected() : isOutputJackConnected();
+        Slog.i(TAG, DIRECTION_STR[direction] + " JACK connected: " + connected);
+        int state = (enable && connected) ? 1 : 0;
+        if (state != mState[direction]) {
+            mState[direction] = state;
+            AudioDeviceAttributes attributes = new AudioDeviceAttributes(
+                    mDeviceType[direction], mAlsaCardDeviceString, mDeviceName);
+            try {
+                mAudioService.setWiredDeviceConnectionState(attributes, state, TAG);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState for "
+                        + DIRECTION_STR[direction]);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Notify AudioService about the input device connection state.
+     *
+     * @param enable true to notify the device as connected.
+     * @return true only when it successfully notifies AudioService about the device
+     *         connection state.
+     */
+    public synchronized boolean updateInputWiredDeviceConnectionState(boolean enable) {
+        return updateWiredDeviceConnectionState(INPUT, enable);
+    }
+
+    /**
+     * Notify AudioService about the output device connection state.
+     *
+     * @param enable true to notify the device as connected.
+     * @return true only when it successfully notifies AudioService about the device
+     *         connection state.
+     */
+    public synchronized boolean updateOutputWiredDeviceConnectionState(boolean enable) {
+        return updateWiredDeviceConnectionState(OUTPUT, enable);
+    }
 
     /**
      * @Override
@@ -249,8 +337,8 @@
         return "UsbAlsaDevice: [card: " + mCardNum
             + ", device: " + mDeviceNum
             + ", name: " + mDeviceName
-            + ", hasOutput: " + mHasOutput
-            + ", hasInput: " + mHasInput + "]";
+            + ", hasOutput: " + mHasDevice[OUTPUT]
+            + ", hasInput: " + mHasDevice[INPUT] + "]";
     }
 
     /**
@@ -262,8 +350,8 @@
         dump.write("card", UsbAlsaDeviceProto.CARD, mCardNum);
         dump.write("device", UsbAlsaDeviceProto.DEVICE, mDeviceNum);
         dump.write("name", UsbAlsaDeviceProto.NAME, mDeviceName);
-        dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasOutput);
-        dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasInput);
+        dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasDevice[OUTPUT]);
+        dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasDevice[INPUT]);
         dump.write("address", UsbAlsaDeviceProto.ADDRESS, mDeviceAddress);
 
         dump.end(token);
@@ -294,10 +382,8 @@
         UsbAlsaDevice other = (UsbAlsaDevice) obj;
         return (mCardNum == other.mCardNum
                 && mDeviceNum == other.mDeviceNum
-                && mHasOutput == other.mHasOutput
-                && mHasInput == other.mHasInput
-                && mIsInputHeadset == other.mIsInputHeadset
-                && mIsOutputHeadset == other.mIsOutputHeadset
+                && Arrays.equals(mHasDevice, other.mHasDevice)
+                && Arrays.equals(mIsHeadset, other.mIsHeadset)
                 && mIsDock == other.mIsDock);
     }
 
@@ -310,10 +396,10 @@
         int result = 1;
         result = prime * result + mCardNum;
         result = prime * result + mDeviceNum;
-        result = prime * result + (mHasOutput ? 0 : 1);
-        result = prime * result + (mHasInput ? 0 : 1);
-        result = prime * result + (mIsInputHeadset ? 0 : 1);
-        result = prime * result + (mIsOutputHeadset ? 0 : 1);
+        result = prime * result + (mHasDevice[OUTPUT] ? 0 : 1);
+        result = prime * result + (mHasDevice[INPUT] ? 0 : 1);
+        result = prime * result + (mIsHeadset[INPUT] ? 0 : 1);
+        result = prime * result + (mIsHeadset[OUTPUT] ? 0 : 1);
         result = prime * result + (mIsDock ? 0 : 1);
 
         return result;
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
index c498847..d4f0b59 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
@@ -81,7 +81,8 @@
             if (mStopJackDetect) {
                 return false;
             }
-            mAlsaDevice.updateWiredDeviceConnectionState(true);
+            mAlsaDevice.updateOutputWiredDeviceConnectionState(true);
+            mAlsaDevice.updateInputWiredDeviceConnectionState(true);
         }
         return true;
     }
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index aa1d556..99881e1 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -20,12 +20,14 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.hardware.usb.UsbDevice;
+import android.media.AudioManager;
 import android.media.IAudioService;
 import android.media.midi.MidiDeviceInfo;
 import android.os.Bundle;
 import android.os.FileObserver;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.service.usb.UsbAlsaManagerProto;
 import android.util.Slog;
@@ -42,6 +44,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Stack;
 
 /**
  * UsbAlsaManager manages USB audio and MIDI devices.
@@ -51,8 +54,9 @@
     private static final boolean DEBUG = false;
 
     // Flag to turn on/off multi-peripheral select mode
-    // Set to true to have single-device-only mode
-    private static final boolean mIsSingleMode = true;
+    // Set to true to have multi-devices mode
+    private static final boolean IS_MULTI_MODE = SystemProperties.getBoolean(
+            "ro.audio.multi_usb_mode", false /*def*/);
 
     private static final String ALSA_DIRECTORY = "/dev/snd/";
 
@@ -70,7 +74,11 @@
     // this is needed to map USB devices to ALSA Audio Devices, especially to remove an
     // ALSA device when we are notified that its associated USB device has been removed.
     private final ArrayList<UsbAlsaDevice> mAlsaDevices = new ArrayList<UsbAlsaDevice>();
-    private UsbAlsaDevice mSelectedDevice;
+    // A map from device type to attached devices. Given the audio framework only supports
+    // single device connection per device type, only the last attached device will be
+    // connected to audio framework. Once the last device is removed, previous device can
+    // be connected to audio framework.
+    private HashMap<Integer, Stack<UsbAlsaDevice>> mAttachedDevices = new HashMap<>();
 
     //
     // Device Denylist
@@ -162,11 +170,6 @@
             Slog.d(TAG, "selectAlsaDevice() " + alsaDevice);
         }
 
-        // This must be where an existing USB audio device is deselected.... (I think)
-        if (mIsSingleMode && mSelectedDevice != null) {
-            deselectAlsaDevice();
-        }
-
         // FIXME Does not yet handle the case where the setting is changed
         // after device connection.  Ideally we should handle the settings change
         // in SettingsObserver. Here we should log that a USB device is connected
@@ -178,21 +181,18 @@
             return;
         }
 
-        mSelectedDevice = alsaDevice;
         alsaDevice.start();
+
         if (DEBUG) {
             Slog.d(TAG, "selectAlsaDevice() - done.");
         }
     }
 
-    private synchronized void deselectAlsaDevice() {
+    private synchronized void deselectAlsaDevice(UsbAlsaDevice selectedDevice) {
         if (DEBUG) {
-            Slog.d(TAG, "deselectAlsaDevice() mSelectedDevice " + mSelectedDevice);
+            Slog.d(TAG, "deselectAlsaDevice() selectedDevice " + selectedDevice);
         }
-        if (mSelectedDevice != null) {
-            mSelectedDevice.stop();
-            mSelectedDevice = null;
-        }
+        selectedDevice.stop();
     }
 
     private int getAlsaDeviceListIndexFor(String deviceAddress) {
@@ -204,32 +204,86 @@
         return -1;
     }
 
-    private UsbAlsaDevice removeAlsaDeviceFromList(String deviceAddress) {
+    private void addDeviceToAttachedDevicesMap(int deviceType, UsbAlsaDevice device) {
+        if (deviceType == AudioManager.DEVICE_NONE) {
+            Slog.i(TAG, "Ignore caching device as the type is NONE, device=" + device);
+            return;
+        }
+        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+        if (devices == null) {
+            mAttachedDevices.put(deviceType, new Stack<>());
+            devices = mAttachedDevices.get(deviceType);
+        }
+        devices.push(device);
+    }
+
+    private void addAlsaDevice(UsbAlsaDevice device) {
+        mAlsaDevices.add(0, device);
+        addDeviceToAttachedDevicesMap(device.getInputDeviceType(), device);
+        addDeviceToAttachedDevicesMap(device.getOutputDeviceType(), device);
+    }
+
+    private void removeDeviceFromAttachedDevicesMap(int deviceType, UsbAlsaDevice device) {
+        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+        if (devices == null) {
+            return;
+        }
+        devices.remove(device);
+        if (devices.isEmpty()) {
+            mAttachedDevices.remove(deviceType);
+        }
+    }
+
+    private UsbAlsaDevice removeAlsaDevice(String deviceAddress) {
         int index = getAlsaDeviceListIndexFor(deviceAddress);
         if (index > -1) {
-            return mAlsaDevices.remove(index);
+            UsbAlsaDevice device = mAlsaDevices.remove(index);
+            removeDeviceFromAttachedDevicesMap(device.getOutputDeviceType(), device);
+            removeDeviceFromAttachedDevicesMap(device.getInputDeviceType(), device);
+            return device;
         } else {
             return null;
         }
     }
 
-    /* package */ UsbAlsaDevice selectDefaultDevice() {
+    private UsbAlsaDevice selectDefaultDevice(int deviceType) {
         if (DEBUG) {
-            Slog.d(TAG, "selectDefaultDevice()");
+            Slog.d(TAG, "selectDefaultDevice():" + deviceType);
         }
 
-        if (mAlsaDevices.size() > 0) {
-            UsbAlsaDevice alsaDevice = mAlsaDevices.get(0);
-            if (DEBUG) {
-                Slog.d(TAG, "  alsaDevice:" + alsaDevice);
-            }
-            if (alsaDevice != null) {
-                selectAlsaDevice(alsaDevice);
-            }
-            return alsaDevice;
-        } else {
+        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+        if (devices == null || devices.isEmpty()) {
             return null;
         }
+        UsbAlsaDevice alsaDevice = devices.peek();
+        Slog.d(TAG, "select default device:" + alsaDevice);
+        if (AudioManager.isInputDevice(deviceType)) {
+            alsaDevice.startInput();
+        } else {
+            alsaDevice.startOutput();
+        }
+        return alsaDevice;
+    }
+
+    private void deselectCurrentDevice(int deviceType) {
+        if (DEBUG) {
+            Slog.d(TAG, "deselectCurrentDevice():" + deviceType);
+        }
+        if (deviceType == AudioManager.DEVICE_NONE) {
+            return;
+        }
+
+        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+        if (devices == null || devices.isEmpty()) {
+            return;
+        }
+        UsbAlsaDevice alsaDevice = devices.peek();
+        Slog.d(TAG, "deselect current device:" + alsaDevice);
+        if (AudioManager.isInputDevice(deviceType)) {
+            alsaDevice.stopInput();
+        } else {
+            alsaDevice.stopOutput();
+        }
     }
 
     /* package */ void usbDeviceAdded(String deviceAddress, UsbDevice usbDevice,
@@ -246,6 +300,7 @@
         AlsaCardsParser.AlsaCardRecord cardRec =
                 mCardsParser.findCardNumFor(deviceAddress);
         if (cardRec == null) {
+            Slog.e(TAG, "usbDeviceAdded(): cannot find sound card for " + deviceAddress);
             return;
         }
 
@@ -275,12 +330,19 @@
                     new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/,
                                       deviceAddress, hasOutput, hasInput,
                                       isInputHeadset, isOutputHeadset, isDock);
-            if (alsaDevice != null) {
-                alsaDevice.setDeviceNameAndDescription(
-                          cardRec.getCardName(), cardRec.getCardDescription());
-                mAlsaDevices.add(0, alsaDevice);
-                selectAlsaDevice(alsaDevice);
+            alsaDevice.setDeviceNameAndDescription(
+                      cardRec.getCardName(), cardRec.getCardDescription());
+            if (IS_MULTI_MODE) {
+                deselectCurrentDevice(alsaDevice.getInputDeviceType());
+                deselectCurrentDevice(alsaDevice.getOutputDeviceType());
+            } else {
+                // At single mode, the first device is the selected device.
+                if (!mAlsaDevices.isEmpty()) {
+                    deselectAlsaDevice(mAlsaDevices.get(0));
+                }
             }
+            addAlsaDevice(alsaDevice);
+            selectAlsaDevice(alsaDevice);
         }
 
         addMidiDevice(deviceAddress, usbDevice, parser, cardRec);
@@ -346,12 +408,20 @@
         }
 
         // Audio
-        UsbAlsaDevice alsaDevice = removeAlsaDeviceFromList(deviceAddress);
+        UsbAlsaDevice alsaDevice = removeAlsaDevice(deviceAddress);
         Slog.i(TAG, "USB Audio Device Removed: " + alsaDevice);
-        if (alsaDevice != null && alsaDevice == mSelectedDevice) {
+        if (alsaDevice != null) {
             waitForAlsaDevice(alsaDevice.getCardNum(), false /*isAdded*/);
-            deselectAlsaDevice();
-            selectDefaultDevice(); // if there any external devices left, select one of them
+            deselectAlsaDevice(alsaDevice);
+            if (IS_MULTI_MODE) {
+                selectDefaultDevice(alsaDevice.getOutputDeviceType());
+                selectDefaultDevice(alsaDevice.getInputDeviceType());
+            } else {
+                // If there are any external devices left, select the latest attached one
+                if (!mAlsaDevices.isEmpty() && mAlsaDevices.get(0) != null) {
+                    selectAlsaDevice(mAlsaDevices.get(0));
+                }
+            }
         }
 
         // MIDI
@@ -362,7 +432,6 @@
         }
 
         logDevices("usbDeviceRemoved()");
-
     }
 
    /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) {
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index 7332d2d..de8d144 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -9,11 +9,60 @@
 
 filegroup {
     name: "services.voiceinteraction-sources",
-    srcs: ["java/**/*.java"],
+    srcs: ["java/com/android/server/voiceinteraction/*.java"],
     path: "java",
     visibility: ["//frameworks/base/services"],
 }
 
+filegroup {
+    name: "services.soundtrigger_middleware-sources",
+    srcs: ["java/com/android/server/soundtrigger_middleware/*.java"],
+    path: "java",
+    visibility: ["//visibility:private"],
+}
+
+filegroup {
+    name: "services.soundtrigger_service-sources",
+    srcs: ["java/com/android/server/soundtrigger/*.java"],
+    path: "java",
+    visibility: ["//visibility:private"],
+}
+
+filegroup {
+    name: "services.soundtrigger-sources",
+    srcs: [
+        ":services.soundtrigger_service-sources",
+        ":services.soundtrigger_middleware-sources",
+    ],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.soundtrigger_middleware",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.soundtrigger_middleware-sources"],
+    libs: [
+        "services.core",
+    ],
+    static_libs: [
+        "android.hardware.soundtrigger-V2.3-java",
+    ],
+    visibility: ["//visibility/base/services/tests/voiceinteraction"],
+}
+
+java_library_static {
+    name: "services.soundtrigger",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.soundtrigger_service-sources"],
+    libs: [
+        "services.core",
+    ],
+    static_libs: [
+        "services.soundtrigger_middleware",
+    ],
+}
+
 java_library_static {
     name: "services.voiceinteraction",
     defaults: ["platform_service_defaults"],
diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index 5fe1c8d..f098155 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -5,6 +5,9 @@
       "options": [
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceStressTest"
         }
       ]
     },
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 5efd158..07dc1c6 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -315,12 +315,13 @@
             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
             int keyphraseId, boolean runInBatterySaverMode) {
         synchronized (mLock) {
+            // TODO Remove previous callback handling
             IRecognitionStatusCallback oldCallback = modelData.getCallback();
             if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) {
                 Slog.w(TAG, "Canceling previous recognition for model id: "
                         + modelData.getModelId());
                 try {
-                    oldCallback.onError(STATUS_ERROR);
+                    oldCallback.onPreempted();
                 } catch (RemoteException e) {
                     Slog.w(TAG, "RemoteException in onDetectionStopped", e);
                 }
@@ -759,15 +760,12 @@
                     onRecognitionAbortLocked(event);
                     break;
                 case SoundTrigger.RECOGNITION_STATUS_FAILURE:
-                    // Fire failures to all listeners since it's not tied to a keyphrase.
-                    onRecognitionFailureLocked();
-                    break;
                 case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
                 case SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE:
                     if (isKeyphraseRecognitionEvent(event)) {
-                        onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
+                        onKeyphraseRecognitionLocked((KeyphraseRecognitionEvent) event);
                     } else {
-                        onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
+                        onGenericRecognitionLocked((GenericRecognitionEvent) event);
                     }
                     break;
             }
@@ -778,7 +776,7 @@
         return event instanceof KeyphraseRecognitionEvent;
     }
 
-    private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
+    private void onGenericRecognitionLocked(GenericRecognitionEvent event) {
         MetricsLogger.count(mContext, "sth_generic_recognition_event", 1);
         if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS
                 && event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
@@ -901,17 +899,6 @@
         }
     }
 
-    private void onRecognitionFailureLocked() {
-        Slog.w(TAG, "Recognition failure");
-        MetricsLogger.count(mContext, "sth_recognition_failure_event", 1);
-        try {
-            sendErrorCallbacksToAllLocked(STATUS_ERROR);
-        } finally {
-            internalClearModelStateLocked();
-            internalClearGlobalStateLocked();
-        }
-    }
-
     private int getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event) {
         if (event == null) {
             Slog.w(TAG, "Null RecognitionEvent received.");
@@ -927,7 +914,7 @@
         return keyphraseExtras[0].id;
     }
 
-    private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
+    private void onKeyphraseRecognitionLocked(KeyphraseRecognitionEvent event) {
         Slog.i(TAG, "Recognition success");
         MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
         int keyphraseId = getKeyphraseIdFromEvent(event);
@@ -1001,7 +988,17 @@
     private void onServiceDiedLocked() {
         try {
             MetricsLogger.count(mContext, "sth_service_died", 1);
-            sendErrorCallbacksToAllLocked(SoundTrigger.STATUS_DEAD_OBJECT);
+            for (ModelData modelData : mModelDataMap.values()) {
+                IRecognitionStatusCallback callback = modelData.getCallback();
+                if (callback != null) {
+                    try {
+                        callback.onModuleDied();
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "RemoteException send moduleDied for model handle " +
+                                modelData.getHandle(), e);
+                    }
+                }
+            }
         } finally {
             internalClearModelStateLocked();
             internalClearGlobalStateLocked();
@@ -1111,21 +1108,6 @@
         }
     }
 
-    // Sends an error callback to all models with a valid registered callback.
-    private void sendErrorCallbacksToAllLocked(int errorCode) {
-        for (ModelData modelData : mModelDataMap.values()) {
-            IRecognitionStatusCallback callback = modelData.getCallback();
-            if (callback != null) {
-                try {
-                    callback.onError(errorCode);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException sendErrorCallbacksToAllLocked for model handle " +
-                            modelData.getHandle(), e);
-                }
-            }
-        }
-    }
-
     /**
      * Stops and unloads all models. This is intended as a clean-up call with the expectation that
      * this instance is not used after.
@@ -1342,11 +1324,11 @@
             // Notify of error if needed.
             if (notifyClientOnError) {
                 try {
-                    callback.onError(status);
+                    callback.onResumeFailed(status);
                 } catch (DeadObjectException e) {
                     forceStopAndUnloadModelLocked(modelData, e);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onError", e);
+                    Slog.w(TAG, "RemoteException in onResumeFailed", e);
                 }
             }
         } else {
@@ -1382,15 +1364,15 @@
         status = mModule.stopRecognition(modelData.getHandle());
 
         if (status != SoundTrigger.STATUS_OK) {
-            Slog.w(TAG, "stopRecognition call failed with " + status);
+            Slog.e(TAG, "stopRecognition call failed with " + status);
             MetricsLogger.count(mContext, "sth_stop_recognition_error", 1);
             if (notify) {
                 try {
-                    callback.onError(status);
+                    callback.onPauseFailed(status);
                 } catch (DeadObjectException e) {
                     forceStopAndUnloadModelLocked(modelData, e);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onError", e);
+                    Slog.w(TAG, "RemoteException in onPauseFailed", e);
                 }
             }
         } else {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 04c1c04..1bbea89 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -25,6 +25,7 @@
 import static android.content.pm.PackageManager.GET_SERVICES;
 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_BAD_VALUE;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_DEAD_OBJECT;
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
 import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY;
@@ -39,12 +40,13 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.hardware.soundtrigger.ConversionUtil;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.ModelParams;
-import android.hardware.soundtrigger.ConversionUtil;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
@@ -64,6 +66,7 @@
 import android.media.soundtrigger.ISoundTriggerDetectionService;
 import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
 import android.media.soundtrigger.SoundTriggerDetectionService;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.os.Binder;
 import android.os.Bundle;
@@ -74,8 +77,8 @@
 import android.os.ParcelUuid;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.ServiceSpecificException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -86,6 +89,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.ISoundTriggerService;
 import com.android.internal.app.ISoundTriggerSession;
+import com.android.server.SoundTriggerInternal;
 import com.android.server.SystemService;
 import com.android.server.utils.EventLogger;
 
@@ -98,8 +102,8 @@
 import java.util.Objects;
 import java.util.TreeMap;
 import java.util.UUID;
-import java.util.stream.Collectors;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * A single SystemService to manage all sound/voice-based sound models on the DSP.
@@ -296,6 +300,23 @@
                 return listUnderlyingModuleProperties(originatorIdentity);
             }
         }
+
+        @Override
+        public void attachInjection(@NonNull ISoundTriggerInjection injection) {
+            if (PermissionChecker.checkCallingPermissionForPreflight(mContext,
+                    android.Manifest.permission.MANAGE_SOUND_TRIGGER, null)
+                        != PermissionChecker.PERMISSION_GRANTED) {
+                throw new SecurityException();
+            }
+            try {
+                ISoundTriggerMiddlewareService.Stub
+                        .asInterface(ServiceManager
+                                .waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE))
+                        .attachFakeHalInjection(injection);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
@@ -1368,8 +1389,7 @@
                         }));
             }
 
-            @Override
-            public void onError(int status) {
+            private void onError(int status) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
 
                 sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
@@ -1392,6 +1412,30 @@
             }
 
             @Override
+            public void onPreempted() {
+                if (DEBUG) Slog.v(TAG, mPuuid + ": onPreempted");
+                onError(STATUS_ERROR);
+            }
+
+            @Override
+            public void onModuleDied() {
+                if (DEBUG) Slog.v(TAG, mPuuid + ": onModuleDied");
+                onError(STATUS_DEAD_OBJECT);
+            }
+
+            @Override
+            public void onResumeFailed(int status) {
+                if (DEBUG) Slog.v(TAG, mPuuid + ": onResumeFailed: " + status);
+                onError(status);
+            }
+
+            @Override
+            public void onPauseFailed(int status) {
+                if (DEBUG) Slog.v(TAG, mPuuid + ": onPauseFailed: " + status);
+                onError(status);
+            }
+
+            @Override
             public void onRecognitionPaused() {
                 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index aaf7a9e..5846ff6 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -39,7 +39,7 @@
  *
  * @hide
  */
-public class DatabaseHelper extends SQLiteOpenHelper {
+public class DatabaseHelper extends SQLiteOpenHelper implements IEnrolledModelDb {
     static final String TAG = "SoundModelDBHelper";
     static final boolean DBG = false;
 
@@ -153,11 +153,7 @@
         }
     }
 
-    /**
-     * Updates the given keyphrase model, adds it, if it doesn't already exist.
-     *
-     * TODO: We only support one keyphrase currently.
-     */
+    @Override
     public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
         synchronized(this) {
             SQLiteDatabase db = getWritableDatabase();
@@ -193,9 +189,7 @@
         }
     }
 
-    /**
-     * Deletes the sound model and associated keyphrases.
-     */
+    @Override
     public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
         // Normalize the locale to guard against SQL injection.
         bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag();
@@ -218,12 +212,7 @@
         }
     }
 
-    /**
-     * Returns a matching {@link KeyphraseSoundModel} for the keyphrase ID.
-     * Returns null if a match isn't found.
-     *
-     * TODO: We only support one keyphrase currently.
-     */
+    @Override
     public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
             String bcp47Locale) {
         // Sanitize the locale to guard against SQL injection.
@@ -237,12 +226,7 @@
         }
     }
 
-    /**
-     * Returns a matching {@link KeyphraseSoundModel} for the keyphrase string.
-     * Returns null if a match isn't found.
-     *
-     * TODO: We only support one keyphrase currently.
-     */
+    @Override
     public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
             String bcp47Locale) {
         // Sanitize the locale to guard against SQL injection.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index f3cb9ba..486945d 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -56,6 +56,7 @@
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.ISandboxedDetectionService;
 import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
+import android.service.voice.SoundTriggerFailure;
 import android.service.voice.VisualQueryDetectionService;
 import android.service.voice.VisualQueryDetectionServiceFailure;
 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
@@ -153,7 +154,8 @@
     private int mRestartCount = 0;
     @NonNull private ServiceConnection mRemoteHotwordDetectionService;
     @NonNull private ServiceConnection mRemoteVisualQueryDetectionService;
-    private IBinder mAudioFlinger;
+    @GuardedBy("mLock")
+    @Nullable private IBinder mAudioFlinger;
     @GuardedBy("mLock")
     private boolean mDebugHotwordLogging = false;
 
@@ -193,7 +195,7 @@
                 new Intent(VisualQueryDetectionService.SERVICE_INTERFACE);
         visualQueryDetectionServiceIntent.setComponent(mVisualQueryDetectionComponentName);
 
-        initAudioFlingerLocked();
+        initAudioFlinger();
 
         mHotwordDetectionServiceConnectionFactory =
                 new ServiceConnectionFactory(hotwordDetectionServiceIntent,
@@ -226,31 +228,41 @@
         }
     }
 
-    private void initAudioFlingerLocked() {
+    private void initAudioFlinger() {
         if (DEBUG) {
-            Slog.d(TAG, "initAudioFlingerLocked");
+            Slog.d(TAG, "initAudioFlinger");
         }
-        mAudioFlinger = ServiceManager.waitForService("media.audio_flinger");
-        if (mAudioFlinger == null) {
+        final IBinder audioFlinger = ServiceManager.waitForService("media.audio_flinger");
+        if (audioFlinger == null) {
+            setAudioFlinger(null);
             throw new IllegalStateException("Service media.audio_flinger wasn't found.");
         }
         if (DEBUG) {
             Slog.d(TAG, "Obtained audio_flinger binder.");
         }
         try {
-            mAudioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
+            audioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
         } catch (RemoteException e) {
             Slog.w(TAG, "Audio server died before we registered a DeathRecipient; "
-                            + "retrying init.", e);
-            initAudioFlingerLocked();
+                    + "retrying init.", e);
+            initAudioFlinger();
+            return;
+        }
+
+        setAudioFlinger(audioFlinger);
+    }
+
+    private void setAudioFlinger(@Nullable IBinder audioFlinger) {
+        synchronized (mLock) {
+            mAudioFlinger = audioFlinger;
         }
     }
 
     private void audioServerDied() {
         Slog.w(TAG, "Audio server died; restarting the HotwordDetectionService.");
+        // TODO: Check if this needs to be scheduled on a different thread.
+        initAudioFlinger();
         synchronized (mLock) {
-            // TODO: Check if this needs to be scheduled on a different thread.
-            initAudioFlingerLocked();
             // We restart the process instead of simply sending over the new binder, to avoid race
             // conditions with audio reading in the service.
             restartProcessLocked();
@@ -576,8 +588,31 @@
         }
 
         @Override
-        public void onError(int status) throws RemoteException {
-            mExternalCallback.onError(status);
+        public void onPreempted() throws RemoteException {
+            mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+                        SoundTriggerFailure.ERROR_CODE_UNEXPECTED_PREEMPTION,
+                        "Unexpected startRecognition on already started ST session"));
+        }
+
+        @Override
+        public void onModuleDied() throws RemoteException {
+            mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+                        SoundTriggerFailure.ERROR_CODE_MODULE_DIED,
+                        "STHAL died"));
+        }
+
+        @Override
+        public void onResumeFailed(int status) throws RemoteException {
+            mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+                        SoundTriggerFailure.ERROR_CODE_RECOGNITION_RESUME_FAILED,
+                        "STService recognition resume failed with: " + status));
+        }
+
+        @Override
+        public void onPauseFailed(int status) throws RemoteException {
+            mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+                        SoundTriggerFailure.ERROR_CODE_RECOGNITION_RESUME_FAILED,
+                        "STService recognition pause failed with: " + status));
         }
 
         @Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java
new file mode 100644
index 0000000..f10c2f6
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java
@@ -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.server.voiceinteraction;
+
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface for registering and querying the enrolled keyphrase model database for
+ * {@link VoiceInteractionManagerService}.
+ * This interface only supports one keyphrase per {@link KeyphraseSoundModel}.
+ * The non-update methods are uniquely keyed on fields of the first keyphrase
+ * {@link KeyphraseSoundModel#getKeyphrases()}.
+ * @hide
+ */
+public interface IEnrolledModelDb {
+
+    //TODO(273286174): We only support one keyphrase currently.
+    /**
+     * Register the given {@link KeyphraseSoundModel}, or updates it if it already exists.
+     *
+     * @param soundModel - The sound model to register in the database.
+     * Updates the sound model if the keyphrase id, users, locale match an existing entry.
+     * Must have one and only one associated {@link Keyphrase}.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel);
+
+    /**
+     * Deletes the previously registered keyphrase sound model from the database.
+     *
+     * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to delete.
+     * @param userHandle - The user handle making this request. Must be included in the user
+     *                     list of the registered sound model.
+     * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale);
+
+    //TODO(273286174): We only support one keyphrase currently.
+    /**
+     * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair,
+     * contingent on the userHandle existing in the user list for the model.
+     * Returns null if a match isn't found.
+     *
+     * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to query.
+     * @param userHandle - The user handle making this request. Must be included in the user
+     *                     list of the registered sound model.
+     * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+            String bcp47Locale);
+
+    //TODO(273286174): We only support one keyphrase currently.
+    /**
+     * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair,
+     * contingent on the userHandle existing in the user list for the model.
+     * Returns null if a match isn't found.
+     *
+     * @param keyphrase - The text of (the first) keyphrase of the KeyphraseSoundModel to query.
+     * @param userHandle - The user handle making this request. Must be included in the user
+     *                     list of the registered sound model.
+     * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+            String bcp47Locale);
+
+    /**
+     * Dumps contents of database for dumpsys
+     */
+    void dump(PrintWriter pw);
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java
new file mode 100644
index 0000000..9bbaf8e
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java
@@ -0,0 +1,148 @@
+/**
+ * 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.voiceinteraction;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * In memory model enrollment database for testing purposes.
+ * @hide
+ */
+public class TestModelEnrollmentDatabase implements IEnrolledModelDb {
+
+    // Record representing the primary key used in the real model database.
+    private static final class EnrollmentKey {
+        private final int mKeyphraseId;
+        private final List<Integer> mUserIds;
+        private final String mLocale;
+
+        EnrollmentKey(int keyphraseId,
+                @NonNull List<Integer> userIds, @NonNull String locale) {
+            mKeyphraseId = keyphraseId;
+            mUserIds = Objects.requireNonNull(userIds);
+            mLocale = Objects.requireNonNull(locale);
+        }
+
+        int keyphraseId() {
+            return mKeyphraseId;
+        }
+
+        List<Integer> userIds() {
+            return mUserIds;
+        }
+
+        String locale() {
+            return mLocale;
+        }
+
+        @Override
+        public String toString() {
+            StringJoiner sj = new StringJoiner(", ", "{", "}");
+            sj.add("keyphraseId: " + mKeyphraseId);
+            sj.add("userIds: " + mUserIds.toString());
+            sj.add("locale: " + mLocale.toString());
+            return "EnrollmentKey: " + sj.toString();
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int res = 1;
+            res = prime * res + mKeyphraseId;
+            res = prime * res + mUserIds.hashCode();
+            res = prime * res + mLocale.hashCode();
+            return res;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) return true;
+            if (other == null) return false;
+            if (!(other instanceof EnrollmentKey)) return false;
+            EnrollmentKey that = (EnrollmentKey) other;
+            if (mKeyphraseId != that.mKeyphraseId) return false;
+            if (!mUserIds.equals(that.mUserIds)) return false;
+            if (!mLocale.equals(that.mLocale)) return false;
+            return true;
+        }
+
+    }
+
+    private final Map<EnrollmentKey, KeyphraseSoundModel> mModelMap = new HashMap<>();
+
+    @Override
+    public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
+        final Keyphrase keyphrase = soundModel.getKeyphrases()[0];
+        mModelMap.put(new EnrollmentKey(keyphrase.getId(),
+                        Arrays.stream(keyphrase.getUsers()).boxed().toList(),
+                        keyphrase.getLocale().toLanguageTag()),
+                    soundModel);
+        return true;
+    }
+
+    @Override
+    public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
+        return mModelMap.keySet().removeIf(key -> (key.keyphraseId() == keyphraseId)
+                && key.locale().equals(bcp47Locale)
+                && key.userIds().contains(userHandle));
+    }
+
+    @Override
+    public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+            String bcp47Locale) {
+        return mModelMap.entrySet()
+                .stream()
+                .filter((entry) -> (entry.getKey().keyphraseId() == keyphraseId)
+                        && entry.getKey().locale().equals(bcp47Locale)
+                        && entry.getKey().userIds().contains(userHandle))
+                .findFirst()
+                .map((entry) -> entry.getValue())
+                .orElse(null);
+    }
+
+    @Override
+    public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+            String bcp47Locale) {
+        return mModelMap.entrySet()
+                .stream()
+                .filter((entry) -> (entry.getValue().getKeyphrases()[0].getText().equals(keyphrase)
+                        && entry.getKey().locale().equals(bcp47Locale)
+                        && entry.getKey().userIds().contains(userHandle)))
+                .findFirst()
+                .map((entry) -> entry.getValue())
+                .orElse(null);
+    }
+
+
+    /**
+     * Dumps contents of database for dumpsys
+     */
+    public void dump(PrintWriter pw) {
+        pw.println("Using test enrollment database, with enrolled models:");
+        pw.println(mModelMap);
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e1da2ca..1d7b966 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -99,11 +99,11 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.SoundTriggerInternal;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
-import com.android.server.soundtrigger.SoundTriggerInternal;
 import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -125,7 +125,9 @@
 
     final Context mContext;
     final ContentResolver mResolver;
-    final DatabaseHelper mDbHelper;
+    // Can be overridden for testing purposes
+    private IEnrolledModelDb mDbHelper;
+    private final IEnrolledModelDb mRealDbHelper;
     final ActivityManagerInternal mAmInternal;
     final ActivityTaskManagerInternal mAtmInternal;
     final UserManagerInternal mUserManagerInternal;
@@ -143,7 +145,7 @@
         mResolver = context.getContentResolver();
         mUserManagerInternal = Objects.requireNonNull(
                 LocalServices.getService(UserManagerInternal.class));
-        mDbHelper = new DatabaseHelper(context);
+        mDbHelper = mRealDbHelper = new DatabaseHelper(context);
         mServiceStub = new VoiceInteractionManagerServiceStub();
         mAmInternal = Objects.requireNonNull(
                 LocalServices.getService(ActivityManagerInternal.class));
@@ -1605,6 +1607,42 @@
             }
         }
 
+        @Override
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+        public void setModelDatabaseForTestEnabled(boolean enabled, IBinder token) {
+            super.setModelDatabaseForTestEnabled_enforcePermission();
+            enforceCallerAllowedToEnrollVoiceModel();
+            synchronized (this) {
+                if (enabled) {
+                    // Replace the dbhelper with a new test db
+                    final var db = new TestModelEnrollmentDatabase();
+                    try {
+                        // Listen to our caller death, and make sure we revert to the real
+                        // db if they left the model in a test state.
+                        token.linkToDeath(() -> {
+                            synchronized (this) {
+                                if (mDbHelper == db) {
+                                    mDbHelper = mRealDbHelper;
+                                    mImpl.notifySoundModelsChangedLocked();
+                                }
+                            }
+                        }, 0);
+                    } catch (RemoteException e) {
+                        // If the caller is already dead, nothing to do.
+                        return;
+                    }
+                    mDbHelper = db;
+                    mImpl.notifySoundModelsChangedLocked();
+                } else {
+                    // Nothing to do if the db is already set to the real impl.
+                    if (mDbHelper != mRealDbHelper) {
+                        mDbHelper = mRealDbHelper;
+                        mImpl.notifySoundModelsChangedLocked();
+                    }
+                }
+            }
+        }
+
         //----------------- SoundTrigger APIs --------------------------------//
         @Override
         public boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale) {
@@ -1712,28 +1750,27 @@
                 final long caller = Binder.clearCallingIdentity();
                 try {
                     KeyphraseSoundModel soundModel =
-                            mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
+                            mDbHelper.getKeyphraseSoundModel(keyphraseId,
+                                    callingUserId, bcp47Locale);
                     if (soundModel == null
                             || soundModel.getUuid() == null
                             || soundModel.getKeyphrases() == null) {
                         Slog.w(TAG, "No matching sound model found in startRecognition");
                         return SoundTriggerInternal.STATUS_ERROR;
-                    } else {
-                        // Regardless of the status of the start recognition, we need to make sure
-                        // that we unload this model if needed later.
-                        synchronized (VoiceInteractionManagerServiceStub.this) {
-                            mLoadedKeyphraseIds.put(keyphraseId, this);
-                            if (mSessionExternalCallback == null
-                                    || mSessionInternalCallback == null
-                                    || callback.asBinder() != mSessionExternalCallback.asBinder()) {
-                                mSessionInternalCallback = createSoundTriggerCallbackLocked(
-                                        callback);
-                                mSessionExternalCallback = callback;
-                            }
-                        }
-                        return mSession.startRecognition(keyphraseId, soundModel,
-                                mSessionInternalCallback, recognitionConfig, runInBatterySaverMode);
                     }
+                    // Regardless of the status of the start recognition, we need to make sure
+                    // that we unload this model if needed later.
+                    synchronized (VoiceInteractionManagerServiceStub.this) {
+                        mLoadedKeyphraseIds.put(keyphraseId, this);
+                        if (mSessionExternalCallback == null
+                                || mSessionInternalCallback == null
+                                || callback.asBinder() != mSessionExternalCallback.asBinder()) {
+                            mSessionInternalCallback = createSoundTriggerCallbackLocked(callback);
+                            mSessionExternalCallback = callback;
+                        }
+                    }
+                    return mSession.startRecognition(keyphraseId, soundModel,
+                            mSessionInternalCallback, recognitionConfig, runInBatterySaverMode);
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 62be2a55..0ad86c1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -71,7 +71,6 @@
 import android.util.Slog;
 import android.view.IWindowManager;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
 import com.android.internal.app.IVoiceActionCheckCallback;
@@ -248,7 +247,6 @@
                 Context.RECEIVER_EXPORTED);
     }
 
-    @GuardedBy("this")
     public void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) {
         final int grantRecipientAppId = UserHandle.getAppId(grantRecipientUid);
         final int grantRecipientUserId = UserHandle.getUserId(grantRecipientUid);
@@ -258,7 +256,6 @@
                 /* direct= */ true);
     }
 
-    @GuardedBy("this")
     public boolean showSessionLocked(@Nullable Bundle args, int flags,
             @Nullable String attributionTag,
             @Nullable IVoiceInteractionSessionShowCallback showCallback,
@@ -331,7 +328,6 @@
         }
     }
 
-    @GuardedBy("this")
     public boolean hideSessionLocked() {
         if (mActiveSession != null) {
             return mActiveSession.hideLocked();
@@ -339,7 +335,6 @@
         return false;
     }
 
-    @GuardedBy("this")
     public boolean deliverNewSessionLocked(IBinder token,
             IVoiceInteractionSession session, IVoiceInteractor interactor) {
         if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -350,7 +345,6 @@
         return true;
     }
 
-    @GuardedBy("this")
     public int startVoiceActivityLocked(@Nullable String callingFeatureId, int callingPid,
             int callingUid, IBinder token, Intent intent, String resolvedType) {
         try {
@@ -373,7 +367,6 @@
         }
     }
 
-    @GuardedBy("this")
     public int startAssistantActivityLocked(@Nullable String callingFeatureId, int callingPid,
             int callingUid, IBinder token, Intent intent, String resolvedType,
             @NonNull Bundle bundle) {
@@ -397,7 +390,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void requestDirectActionsLocked(@NonNull IBinder token, int taskId,
             @NonNull IBinder assistToken,  @Nullable RemoteCallback cancellationCallback,
             @NonNull RemoteCallback callback) {
@@ -453,7 +445,6 @@
         }
     }
 
-    @GuardedBy("this")
     void performDirectActionLocked(@NonNull IBinder token, @NonNull String actionId,
             @Nullable Bundle arguments, int taskId, IBinder assistToken,
             @Nullable RemoteCallback cancellationCallback,
@@ -480,7 +471,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void setKeepAwakeLocked(IBinder token, boolean keepAwake) {
         try {
             if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -493,7 +483,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void closeSystemDialogsLocked(IBinder token) {
         try {
             if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -506,7 +495,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void finishLocked(IBinder token, boolean finishTask) {
         if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) {
             Slog.w(TAG, "finish does not match active session");
@@ -516,7 +504,6 @@
         mActiveSession = null;
     }
 
-    @GuardedBy("this")
     public void setDisabledShowContextLocked(int callingUid, int flags) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -526,7 +513,6 @@
         mDisabledShowContext = flags;
     }
 
-    @GuardedBy("this")
     public int getDisabledShowContextLocked(int callingUid) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -536,7 +522,6 @@
         return mDisabledShowContext;
     }
 
-    @GuardedBy("this")
     public int getUserDisabledShowContextLocked(int callingUid) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -550,7 +535,6 @@
         return mInfo.getSupportsLocalInteraction();
     }
 
-    @GuardedBy("this")
     public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
         if (DEBUG) {
             Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token);
@@ -563,7 +547,6 @@
         mActiveSession.startListeningVisibleActivityChangedLocked();
     }
 
-    @GuardedBy("this")
     public void stopListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
         if (DEBUG) {
             Slog.d(TAG, "stopListeningVisibleActivityChangedLocked: token=" + token);
@@ -576,7 +559,6 @@
         mActiveSession.stopListeningVisibleActivityChangedLocked();
     }
 
-    @GuardedBy("this")
     public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
         if (DEBUG) {
             Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
@@ -591,7 +573,6 @@
         mActiveSession.notifyActivityDestroyedLocked(activityToken);
     }
 
-    @GuardedBy("this")
     public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
         if (DEBUG) {
             Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
@@ -606,7 +587,6 @@
         mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
     }
 
-    @GuardedBy("this")
     public void updateStateLocked(
             @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory,
@@ -627,7 +607,6 @@
         }
     }
 
-    @GuardedBy("this")
     private void verifyDetectorForHotwordDetectionLocked(
             @Nullable SharedMemory sharedMemory,
             IHotwordRecognitionStatusCallback callback,
@@ -685,7 +664,6 @@
                 voiceInteractionServiceUid);
     }
 
-    @GuardedBy("this")
     private void verifyDetectorForVisualQueryDetectionLocked(@Nullable SharedMemory sharedMemory) {
         Slog.v(TAG, "verifyDetectorForVisualQueryDetectionLocked");
 
@@ -724,7 +702,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void initAndVerifyDetectorLocked(
             @NonNull Identity voiceInteractorIdentity,
             @Nullable PersistableBundle options,
@@ -769,7 +746,6 @@
                 detectorType);
     }
 
-    @GuardedBy("this")
     public void destroyDetectorLocked(IBinder token) {
         Slog.v(TAG, "destroyDetectorLocked");
 
@@ -788,7 +764,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void shutdownHotwordDetectionServiceLocked() {
         if (DEBUG) {
             Slog.d(TAG, "shutdownHotwordDetectionServiceLocked");
@@ -801,7 +776,6 @@
         mHotwordDetectionConnection = null;
     }
 
-    @GuardedBy("this")
     public void setVisualQueryDetectionAttentionListenerLocked(
             @Nullable IVisualQueryDetectionAttentionListener listener) {
         if (mHotwordDetectionConnection == null) {
@@ -810,7 +784,6 @@
         mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
     }
 
-    @GuardedBy("this")
     public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startPerceivingLocked");
@@ -824,7 +797,6 @@
         mHotwordDetectionConnection.startPerceivingLocked(callback);
     }
 
-    @GuardedBy("this")
     public void stopPerceivingLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopPerceivingLocked");
@@ -838,7 +810,6 @@
         mHotwordDetectionConnection.stopPerceivingLocked();
     }
 
-    @GuardedBy("this")
     public void startListeningFromMicLocked(
             AudioFormat audioFormat,
             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
@@ -854,7 +825,6 @@
         mHotwordDetectionConnection.startListeningFromMicLocked(audioFormat, callback);
     }
 
-    @GuardedBy("this")
     public void startListeningFromExternalSourceLocked(
             ParcelFileDescriptor audioStream,
             AudioFormat audioFormat,
@@ -879,7 +849,6 @@
                 options, token, callback);
     }
 
-    @GuardedBy("this")
     public void stopListeningFromMicLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopListeningFromMicLocked");
@@ -893,7 +862,6 @@
         mHotwordDetectionConnection.stopListeningFromMicLocked();
     }
 
-    @GuardedBy("this")
     public void triggerHardwareRecognitionEventForTestLocked(
             SoundTrigger.KeyphraseRecognitionEvent event,
             IHotwordRecognitionStatusCallback callback) {
@@ -908,7 +876,6 @@
         mHotwordDetectionConnection.triggerHardwareRecognitionEventForTestLocked(event, callback);
     }
 
-    @GuardedBy("this")
     public IRecognitionStatusCallback createSoundTriggerCallbackLocked(
             IHotwordRecognitionStatusCallback callback) {
         if (DEBUG) {
@@ -933,12 +900,11 @@
         return null;
     }
 
-    @GuardedBy("this")
     boolean isIsolatedProcessLocked(@NonNull ServiceInfo serviceInfo) {
         return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
                 && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
     }
-    @GuardedBy("this")
+
     boolean verifyProcessSharingLocked() {
         // only check this if both VQDS and HDS are declared in the app
         ServiceInfo hotwordInfo = getServiceInfoLocked(mHotwordDetectionComponentName, mUser);
@@ -960,7 +926,6 @@
         mHotwordDetectionConnection.forceRestart();
     }
 
-    @GuardedBy("this")
     void setDebugHotwordLoggingLocked(boolean logging) {
         if (mHotwordDetectionConnection == null) {
             Slog.w(TAG, "Failed to set temporary debug logging: no hotword detection active");
@@ -969,7 +934,6 @@
         mHotwordDetectionConnection.setDebugHotwordLoggingLocked(logging);
     }
 
-    @GuardedBy("this")
     void resetHotwordDetectionConnectionLocked() {
         if (DEBUG) {
             Slog.d(TAG, "resetHotwordDetectionConnectionLocked");
@@ -984,7 +948,6 @@
         mHotwordDetectionConnection = null;
     }
 
-    @GuardedBy("this")
     public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!mValid) {
             pw.print("  NOT VALID: ");
@@ -1023,7 +986,6 @@
         }
     }
 
-    @GuardedBy("this")
     void startLocked() {
         Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
         intent.setComponent(mComponent);
@@ -1048,7 +1010,6 @@
         }
     }
 
-    @GuardedBy("this")
     void shutdownLocked() {
         // If there is an active session, cancel it to allow it to clean up its window and other
         // state.
@@ -1076,7 +1037,6 @@
         }
     }
 
-    @GuardedBy("this")
     void notifySoundModelsChangedLocked() {
         if (mService == null) {
             Slog.w(TAG, "Not bound to voice interaction service " + mComponent);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index e39af5a..9dd2a61 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2682,71 +2682,76 @@
     }
 
     /**
-     * Reports a new call with the specified {@link CallAttributes} to the telecom service. This
-     * method can be used to report both incoming and outgoing calls.  By reporting the call, the
-     * system is aware of the call and can provide updates on services (ex. Another device wants to
-     * disconnect the call) or events (ex. a new Bluetooth route became available).
-     *
+     * Add a call to the Android system service Telecom. This allows the system to start tracking an
+     * incoming or outgoing call with the specified {@link CallAttributes}. Once the call is ready
+     * to be disconnected, use the {@link CallControl#disconnect(DisconnectCause, Executor,
+     * OutcomeReceiver)} which is provided by the {@code pendingControl#onResult(CallControl)}.
      * <p>
-     * The difference between this API call and {@link TelecomManager#placeCall(Uri, Bundle)} or
-     * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)} is that this API
-     * will asynchronously provide an update on whether the new call was added successfully via
-     * an {@link OutcomeReceiver}.  Additionally, callbacks will run on the executor thread that was
-     * passed in.
-     *
      * <p>
-     * Note: Only packages that register with
+     * <p>
+     * <b>Call Lifecycle</b>: Your app is given foreground execution priority as long as you have a
+     * valid call and are posting a {@link android.app.Notification.CallStyle} notification.
+     * When your application is given foreground execution priority, your app is treated as a
+     * foreground service. Foreground execution priority will prevent the
+     * {@link android.app.ActivityManager} from killing your application when it is placed the
+     * background. Foreground execution priority is removed from your app when all of your app's
+     * calls terminate or your app no longer posts a valid notification.
+     * <p>
+     * <p>
+     * <p>
+     * <b>Note</b>: Only packages that register with
      * {@link PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS}
      * can utilize this API. {@link PhoneAccount}s that set the capabilities
      * {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION},
      * {@link PhoneAccount#CAPABILITY_CALL_PROVIDER},
      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}
      * are not supported and will cause an exception to be thrown.
-     *
      * <p>
-     * Usage example:
+     * <p>
+     * <p>
+     * <b>Usage example:</b>
      * <pre>
-     *
-     *  // An app should first define their own construct of a Call that overrides all the
-     *  // {@link CallControlCallback}s and {@link CallEventCallback}s
-     *  private class MyVoipCall {
-     *   public String callId = "";
-     *
-     *   public CallControlCallEventCallback handshakes = new
-     *                       CallControlCallEventCallback() {
-     *                         // override/ implement all {@link CallControlCallback}s
+     *  // Its up to your app on how you want to wrap the objects. One such implementation can be:
+     *  class MyVoipCall {
+     *    ...
+     *      public CallControlCallEventCallback handshakes = new  CallControlCallback() {
+     *                         ...
      *                        }
-     *   public CallEventCallback events = new
-     *                       CallEventCallback() {
-     *                         // override/ implement all {@link CallEventCallback}s
+     *
+     *      public CallEventCallback events = new CallEventCallback() {
+     *                         ...
      *                        }
-     *   public MyVoipCall(String id){
-     *       callId = id;
+     *
+     *      public MyVoipCall(String id){
+     *          ...
+     *      }
      *  }
      *
-     * PhoneAccountHandle handle = new PhoneAccountHandle(
-     *                          new ComponentName("com.example.voip.app",
-     *                                            "com.example.voip.app.NewCallActivity"), "123");
-     *
-     * CallAttributes callAttributes = new CallAttributes.Builder(handle,
-     *                                             CallAttributes.DIRECTION_OUTGOING,
-     *                                            "John Smith", Uri.fromParts("tel", "123", null))
-     *                                            .build();
-     *
      * MyVoipCall myFirstOutgoingCall = new MyVoipCall("1");
      *
-     * telecomManager.addCall(callAttributes, Runnable::run, new OutcomeReceiver() {
+     * telecomManager.addCall(callAttributes,
+     *                        Runnable::run,
+     *                        new OutcomeReceiver() {
      *                              public void onResult(CallControl callControl) {
-     *                                 // The call has been added successfully
+     *                                 // The call has been added successfully. For demonstration
+     *                                 // purposes, the call is disconnected immediately ...
+     *                                 callControl.disconnect(
+     *                                                 new DisconnectCause(DisconnectCause.LOCAL) )
      *                              }
-     *                           }, myFirstOutgoingCall.handshakes, myFirstOutgoingCall.events);
+     *                           },
+     *                           myFirstOutgoingCall.handshakes,
+     *                           myFirstOutgoingCall.events);
      * </pre>
      *
-     * @param callAttributes    attributes of the new call (incoming or outgoing, address, etc. )
-     * @param executor          thread to run background CallEventCallback updates on
-     * @param pendingControl    OutcomeReceiver that receives the result of addCall transaction
-     * @param handshakes        object that overrides {@link CallControlCallback}s
-     * @param events            object that overrides {@link CallEventCallback}s
+     * @param callAttributes attributes of the new call (incoming or outgoing, address, etc.)
+     * @param executor       execution context to run {@link CallControlCallback} updates on
+     * @param pendingControl Receives the result of addCall transaction. Upon success, a
+     *                       CallControl object is provided which can be used to do things like
+     *                       disconnect the call that was added.
+     * @param handshakes     callback that receives <b>actionable</b> updates that originate from
+     *                       Telecom.
+     * @param events         callback that receives <b>non</b>-actionable updates that originate
+     *                       from Telecom.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS)
     @SuppressLint("SamShouldBeLast")
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 78c6196..559faf9 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3142,10 +3142,10 @@
      *
      * Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns
      * true). To check for permissions for non-embedded subscription as well,
+     * see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
      *
      * @param info The subscription to check.
      * @return whether the app is authorized to manage this subscription per its metadata.
-     *
      * @see android.telephony.TelephonyManager#hasCarrierPrivileges
      */
     public boolean canManageSubscription(SubscriptionInfo info) {
@@ -3159,12 +3159,12 @@
      *
      * Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns
      * true). To check for permissions for non-embedded subscription as well,
+     * see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
      *
      * @param info The subscription to check.
      * @param packageName Package name of the app to check.
      *
      * @return whether the app is authorized to manage this subscription per its access rules.
-     *
      * @see android.telephony.TelephonyManager#hasCarrierPrivileges
      * @hide
      */
diff --git a/core/java/com/android/internal/expresslog/Utils.java b/telephony/java/android/telephony/satellite/AntennaDirection.aidl
similarity index 69%
copy from core/java/com/android/internal/expresslog/Utils.java
copy to telephony/java/android/telephony/satellite/AntennaDirection.aidl
index d82192f..c838f6f 100644
--- a/core/java/com/android/internal/expresslog/Utils.java
+++ b/telephony/java/android/telephony/satellite/AntennaDirection.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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
+ *     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,
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.expresslog;
+package android.telephony.satellite;
 
-final class Utils {
-    static native long hashString(String stringToHash);
-}
+parcelable AntennaDirection;
diff --git a/telephony/java/android/telephony/satellite/AntennaDirection.java b/telephony/java/android/telephony/satellite/AntennaDirection.java
new file mode 100644
index 0000000..02b0bc7
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaDirection.java
@@ -0,0 +1,138 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Antenna direction is provided as X/Y/Z values corresponding to the direction of the antenna
+ * main lobe as a unit vector in CTIA coordinate system (as specified in Appendix A of Wireless
+ * device CTIA OTAn test plan). CTIA coordinate system is defined relative to device’s screen
+ * when the device is held in default portrait mode with screen facing the user:
+ *
+ * Z axis is vertical along the plane of the device with positive Z pointing up and negative z
+ * pointing towards bottom of the device
+ * Y axis is horizontal along the plane of the device with positive Y pointing towards right of
+ * the phone screen and negative Y pointing towards left
+ * X axis is orthogonal to the Y-Z plane (phone screen), pointing away from the phone screen for
+ * positive X and pointing away from back of the phone for negative X.
+ * @hide
+ */
+public final class AntennaDirection implements Parcelable {
+    /** Antenna x axis direction. */
+    private float mX;
+
+    /** Antenna y axis direction. */
+    private float mY;
+
+    /** Antenna z axis direction. */
+    private float mZ;
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public AntennaDirection(float x, float y, float z) {
+        mX = x;
+        mY = y;
+        mZ = z;
+    }
+
+    private AntennaDirection(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeFloat(mX);
+        out.writeFloat(mY);
+        out.writeFloat(mZ);
+    }
+
+    @NonNull
+    public static final Creator<AntennaDirection> CREATOR =
+            new Creator<>() {
+                @Override
+                public AntennaDirection createFromParcel(Parcel in) {
+                    return new AntennaDirection(in);
+                }
+
+                @Override
+                public AntennaDirection[] newArray(int size) {
+                    return new AntennaDirection[size];
+                }
+            };
+
+    @Override
+    @NonNull public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("X:");
+        sb.append(mX);
+        sb.append(",");
+
+        sb.append("Y:");
+        sb.append(mY);
+        sb.append(",");
+
+        sb.append("Z:");
+        sb.append(mZ);
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        AntennaDirection that = (AntennaDirection) o;
+        return mX == that.mX
+                && mY == that.mY
+                && mZ == that.mZ;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mX, mY, mZ);
+    }
+
+    public float getX() {
+        return mX;
+    }
+
+    public float getY() {
+        return mY;
+    }
+
+    public float getZ() {
+        return mZ;
+    }
+
+    private void readFromParcel(Parcel in) {
+        mX = in.readFloat();
+        mY = in.readFloat();
+        mZ = in.readFloat();
+    }
+}
diff --git a/core/java/com/android/internal/expresslog/Utils.java b/telephony/java/android/telephony/satellite/AntennaPosition.aidl
similarity index 69%
copy from core/java/com/android/internal/expresslog/Utils.java
copy to telephony/java/android/telephony/satellite/AntennaPosition.aidl
index d82192f..0052562 100644
--- a/core/java/com/android/internal/expresslog/Utils.java
+++ b/telephony/java/android/telephony/satellite/AntennaPosition.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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
+ *     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,
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.expresslog;
+package android.telephony.satellite;
 
-final class Utils {
-    static native long hashString(String stringToHash);
-}
+parcelable AntennaPosition;
diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.java b/telephony/java/android/telephony/satellite/AntennaPosition.java
new file mode 100644
index 0000000..eefc8b0
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaPosition.java
@@ -0,0 +1,117 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Antenna Position received from satellite modem which gives information about antenna
+ * direction to be used with satellite communication and suggested device hold positions.
+ * @hide
+ */
+public final class AntennaPosition implements Parcelable {
+    /** Antenna direction used for satellite communication. */
+    @NonNull AntennaDirection mAntennaDirection;
+
+    /** Enum corresponding to device hold position to be used by the end user. */
+    @SatelliteManager.DeviceHoldPosition int mSuggestedHoldPosition;
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public AntennaPosition(@NonNull AntennaDirection antennaDirection, int suggestedHoldPosition) {
+        mAntennaDirection = antennaDirection;
+        mSuggestedHoldPosition = suggestedHoldPosition;
+    }
+
+    private AntennaPosition(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeParcelable(mAntennaDirection, flags);
+        out.writeInt(mSuggestedHoldPosition);
+    }
+
+    @NonNull
+    public static final Creator<AntennaPosition> CREATOR =
+            new Creator<>() {
+                @Override
+                public AntennaPosition createFromParcel(Parcel in) {
+                    return new AntennaPosition(in);
+                }
+
+                @Override
+                public AntennaPosition[] newArray(int size) {
+                    return new AntennaPosition[size];
+                }
+            };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        AntennaPosition that = (AntennaPosition) o;
+        return Objects.equals(mAntennaDirection, that.mAntennaDirection)
+                && mSuggestedHoldPosition == that.mSuggestedHoldPosition;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAntennaDirection, mSuggestedHoldPosition);
+    }
+
+    @Override
+    @NonNull public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("antennaDirection:");
+        sb.append(mAntennaDirection);
+        sb.append(",");
+
+        sb.append("suggestedHoldPosition:");
+        sb.append(mSuggestedHoldPosition);
+        return sb.toString();
+    }
+
+    @NonNull
+    public AntennaDirection getAntennaDirection() {
+        return mAntennaDirection;
+    }
+
+    @SatelliteManager.DeviceHoldPosition
+    public int getSuggestedHoldPosition() {
+        return mSuggestedHoldPosition;
+    }
+
+    private void readFromParcel(Parcel in) {
+        mAntennaDirection = in.readParcelable(AntennaDirection.class.getClassLoader(),
+                AntennaDirection.class);
+        mSuggestedHoldPosition = in.readInt();
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
index 87c8db3..0092890 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -21,7 +21,10 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -44,15 +47,25 @@
     private int mMaxBytesPerOutgoingDatagram;
 
     /**
+     * Antenna Position received from satellite modem which gives information about antenna
+     * direction to be used with satellite communication and suggested device hold positions.
+     * Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition
+     */
+    @NonNull
+    private Map<Integer, AntennaPosition> mAntennaPositionMap;
+
+    /**
      * @hide
      */
     @UnsupportedAppUsage
     public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies,
-            boolean isPointingRequired, int maxBytesPerOutgoingDatagram) {
+            boolean isPointingRequired, int maxBytesPerOutgoingDatagram,
+            @NonNull Map<Integer, AntennaPosition> antennaPositionMap) {
         mSupportedRadioTechnologies = supportedRadioTechnologies == null
                 ? new HashSet<>() : supportedRadioTechnologies;
         mIsPointingRequired = isPointingRequired;
         mMaxBytesPerOutgoingDatagram = maxBytesPerOutgoingDatagram;
+        mAntennaPositionMap = antennaPositionMap;
     }
 
     private SatelliteCapabilities(Parcel in) {
@@ -77,6 +90,17 @@
 
         out.writeBoolean(mIsPointingRequired);
         out.writeInt(mMaxBytesPerOutgoingDatagram);
+
+        if (mAntennaPositionMap != null && !mAntennaPositionMap.isEmpty()) {
+            int size = mAntennaPositionMap.size();
+            out.writeInt(size);
+            for (Map.Entry<Integer, AntennaPosition> entry : mAntennaPositionMap.entrySet()) {
+                out.writeInt(entry.getKey());
+                out.writeParcelable(entry.getValue(), flags);
+            }
+        } else {
+            out.writeInt(0);
+        }
     }
 
     @NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() {
@@ -109,11 +133,32 @@
         sb.append(mIsPointingRequired);
         sb.append(",");
 
-        sb.append("maxBytesPerOutgoingDatagram");
+        sb.append("maxBytesPerOutgoingDatagram:");
         sb.append(mMaxBytesPerOutgoingDatagram);
+        sb.append(",");
+
+        sb.append("antennaPositionMap:");
+        sb.append(mAntennaPositionMap);
         return sb.toString();
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SatelliteCapabilities that = (SatelliteCapabilities) o;
+        return Objects.equals(mSupportedRadioTechnologies, that.mSupportedRadioTechnologies)
+                && mIsPointingRequired == that.mIsPointingRequired
+                && mMaxBytesPerOutgoingDatagram == that.mMaxBytesPerOutgoingDatagram
+                && Objects.equals(mAntennaPositionMap, that.mAntennaPositionMap);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSupportedRadioTechnologies, mIsPointingRequired,
+                mMaxBytesPerOutgoingDatagram, mAntennaPositionMap);
+    }
+
     /**
      * @return The list of technologies supported by the satellite modem.
      */
@@ -141,6 +186,16 @@
         return mMaxBytesPerOutgoingDatagram;
     }
 
+    /**
+     * Antenna Position received from satellite modem which gives information about antenna
+     * direction to be used with satellite communication and suggested device hold positions.
+     * @return Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition
+     */
+    @NonNull
+    public Map<Integer, AntennaPosition> getAntennaPositionMap() {
+        return mAntennaPositionMap;
+    }
+
     private void readFromParcel(Parcel in) {
         mSupportedRadioTechnologies = new HashSet<>();
         int numSupportedRadioTechnologies = in.readInt();
@@ -152,5 +207,14 @@
 
         mIsPointingRequired = in.readBoolean();
         mMaxBytesPerOutgoingDatagram = in.readInt();
+
+        mAntennaPositionMap = new HashMap<>();
+        int antennaPositionMapSize = in.readInt();
+        for (int i = 0; i < antennaPositionMapSize; i++) {
+            int key = in.readInt();
+            AntennaPosition antennaPosition = in.readParcelable(
+                    AntennaPosition.class.getClassLoader(), AntennaPosition.class);
+            mAntennaPositionMap.put(key, antennaPosition);
+        }
     }
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 20f9bc8b..5681ab2 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -334,6 +334,46 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface NTRadioTechnology {}
 
+    /** Suggested device hold position is unknown. */
+    public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0;
+    /** User is suggested to hold the device in portrait mode. */
+    public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1;
+    /** User is suggested to hold the device in landscape mode with left hand. */
+    public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2;
+    /** User is suggested to hold the device in landscape mode with right hand. */
+    public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3;
+
+    /** @hide */
+    @IntDef(prefix = {"DEVICE_HOLD_POSITION_"}, value = {
+            DEVICE_HOLD_POSITION_UNKNOWN,
+            DEVICE_HOLD_POSITION_PORTRAIT,
+            DEVICE_HOLD_POSITION_LANDSCAPE_LEFT,
+            DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT
+       })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DeviceHoldPosition {}
+
+    /** Display mode is unknown. */
+    public static final int DISPLAY_MODE_UNKNOWN = 0;
+    /** Display mode of the device used for satellite communication for non-foldable phones. */
+    public static final int DISPLAY_MODE_FIXED = 1;
+    /** Display mode of the device used for satellite communication for foldabale phones when the
+     * device is opened. */
+    public static final int DISPLAY_MODE_OPENED = 2;
+    /** Display mode of the device used for satellite communication for foldabable phones when the
+     * device is closed. */
+    public static final int DISPLAY_MODE_CLOSED = 3;
+
+    /** @hide */
+    @IntDef(prefix = {"ANTENNA_POSITION_"}, value = {
+            DISPLAY_MODE_UNKNOWN,
+            DISPLAY_MODE_FIXED,
+            DISPLAY_MODE_OPENED,
+            DISPLAY_MODE_CLOSED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DisplayMode {}
+
     /**
      * Request to enable or disable the satellite modem and demo mode. If the satellite modem is
      * enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
index cd69da1..eaf96ab 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
@@ -17,7 +17,7 @@
 package android.telephony.satellite.stub;
 
 import android.telephony.satellite.stub.NTRadioTechnology;
-
+import android.telephony.satellite.AntennaPosition;
 /**
  * {@hide}
  */
@@ -36,4 +36,14 @@
      * The maximum number of bytes per datagram that can be sent over satellite.
      */
     int maxBytesPerOutgoingDatagram;
+
+    /**
+     * Keys which are used to fill mAntennaPositionMap.
+     */
+    int[] antennaPositionKeys;
+
+    /**
+     * Antenna Position for different display modes received from satellite modem.
+     */
+    AntennaPosition[] antennaPositionValues;
 }
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index f2ffc19..7272abb 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -29,6 +29,7 @@
         <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
         <option name="teardown-command" value="settings delete system show_touches" />
         <option name="teardown-command" value="settings delete system pointer_location" />
+        <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index f8d885a..d7fa124 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -417,9 +417,9 @@
                         int failureReason, int mitigationCount) {
                     if (versionedPackage.getVersionCode() == VERSION_CODE) {
                         // Only rollback for specific versionCode
-                        return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+                        return PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
                     }
-                    return PackageHealthObserverImpact.USER_IMPACT_NONE;
+                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
                 }
             };
 
@@ -442,13 +442,13 @@
     public void testPackageFailureNotifyAllDifferentImpacts() throws Exception {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observerNone = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_NONE);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
         TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         TestObserver observerMid = new TestObserver(OBSERVER_NAME_3,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
         TestObserver observerLow = new TestObserver(OBSERVER_NAME_4,
-                PackageHealthObserverImpact.USER_IMPACT_LOW);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
 
         // Start observing for all impact observers
         watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
@@ -499,9 +499,9 @@
     public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_LOW);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
         TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing for observerFirst and observerSecond with failure handling
         watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
@@ -517,7 +517,7 @@
         assertThat(observerSecond.mMitigatedPackages).isEmpty();
 
         // After observerFirst handles failure, next action it has is high impact
-        observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH;
+        observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
         observerFirst.mMitigatedPackages.clear();
         observerSecond.mMitigatedPackages.clear();
 
@@ -531,7 +531,7 @@
         assertThat(observerFirst.mMitigatedPackages).isEmpty();
 
         // After observerSecond handles failure, it has no further actions
-        observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
+        observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
         observerFirst.mMitigatedPackages.clear();
         observerSecond.mMitigatedPackages.clear();
 
@@ -545,7 +545,7 @@
         assertThat(observerSecond.mMitigatedPackages).isEmpty();
 
         // After observerFirst handles failure, it too has no further actions
-        observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
+        observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
         observerFirst.mMitigatedPackages.clear();
         observerSecond.mMitigatedPackages.clear();
 
@@ -566,9 +566,9 @@
     public void testPackageFailureNotifyOneSameImpact() throws Exception {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
 
         // Start observing for observer1 and observer2 with failure handling
         watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
@@ -592,11 +592,11 @@
         TestController controller = new TestController();
         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         TestObserver observer3 = new TestObserver(OBSERVER_NAME_3,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
 
 
         // Start observing with explicit health checks for APP_A and APP_B respectively
@@ -645,7 +645,7 @@
         TestController controller = new TestController();
         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing with explicit health checks for APP_A and APP_B
         controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C));
@@ -711,7 +711,7 @@
         TestController controller = new TestController();
         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing with explicit health checks for APP_A and
         // package observation duration == LONG_DURATION
@@ -742,7 +742,7 @@
         TestController controller = new TestController();
         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing with explicit health checks for APP_A and
         // package observation duration == SHORT_DURATION / 2
@@ -818,7 +818,7 @@
 
         // Start observing with failure handling
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
 
         // Notify of NetworkStack failure
@@ -1073,9 +1073,9 @@
     public void testBootLoopMitigationDoneForLowestUserImpact() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1);
-        bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LOW);
+        bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
         TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
-        bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+        bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
         watchdog.registerHealthObserver(bootObserver1);
         watchdog.registerHealthObserver(bootObserver2);
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
@@ -1446,7 +1446,7 @@
 
         TestObserver(String name) {
             mName = name;
-            mImpact = PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+            mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
         }
 
         TestObserver(String name, int impact) {
diff --git a/tests/testables/src/android/testing/TestableSettingsProvider.java b/tests/testables/src/android/testing/TestableSettingsProvider.java
index c6f18fd..b850cb8 100644
--- a/tests/testables/src/android/testing/TestableSettingsProvider.java
+++ b/tests/testables/src/android/testing/TestableSettingsProvider.java
@@ -72,7 +72,11 @@
 
     public Bundle call(String method, String arg, Bundle extras) {
         // Methods are "GET_system", "GET_global", "PUT_secure", etc.
-        final int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, UserHandle.myUserId());
+        int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, UserHandle.USER_CURRENT);
+        if (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF) {
+            userId = UserHandle.myUserId();
+        }
+
         final String[] commands = method.split("_", 2);
         final String op = commands[0];
         final String table = commands[1];
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
index 2bfb04f..1731f6b 100644
--- a/tests/testables/tests/AndroidManifest.xml
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -21,7 +21,7 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
 
-    <application android:debuggable="true">
+    <application android:debuggable="true" android:testOnly="true">
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml
new file mode 100644
index 0000000..6d29794
--- /dev/null
+++ b/tests/testables/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Runs Testable Tests.">
+    <option name="test-tag" value="TestablesTests" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="TestablesTests.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.testables"/>
+    </test>
+</configuration>
diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h
index a146466..e2c1614 100644
--- a/tools/aapt/SdkConstants.h
+++ b/tools/aapt/SdkConstants.h
@@ -49,6 +49,7 @@
     SDK_S = 31,
     SDK_S_V2 = 32,
     SDK_TIRAMISU = 33,
+    SDK_UPSIDE_DOWN_CAKE = 34,
     SDK_CUR_DEVELOPMENT = 10000,
 };
 
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 40bcef7..e47704ea 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -59,6 +59,7 @@
   SDK_S = 31,
   SDK_S_V2 = 32,
   SDK_TIRAMISU = 33,
+  SDK_UPSIDE_DOWN_CAKE = 34,
   SDK_CUR_DEVELOPMENT = 10000,
 };
 
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 25fbabc..166fbdd 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -163,11 +163,11 @@
         /**
          * Sets the displayed connection strength of the remote device to the internet.
          *
-         * @param connectionStrength Connection strength in range 0 to 3.
+         * @param connectionStrength Connection strength in range 0 to 4.
          * @return Returns the Builder object.
          */
         @NonNull
-        public Builder setConnectionStrength(@IntRange(from = 0, to = 3) int connectionStrength) {
+        public Builder setConnectionStrength(@IntRange(from = 0, to = 4) int connectionStrength) {
             mConnectionStrength = connectionStrength;
             return this;
         }
@@ -205,8 +205,8 @@
         if (batteryPercentage < 0 || batteryPercentage > 100) {
             throw new IllegalArgumentException("BatteryPercentage must be in range 0-100");
         }
-        if (connectionStrength < 0 || connectionStrength > 3) {
-            throw new IllegalArgumentException("ConnectionStrength must be in range 0-3");
+        if (connectionStrength < 0 || connectionStrength > 4) {
+            throw new IllegalArgumentException("ConnectionStrength must be in range 0-4");
         }
     }
 
@@ -265,9 +265,9 @@
     /**
      * Gets the displayed connection strength of the remote device to the internet.
      *
-     * @return Returns the connection strength in range 0 to 3.
+     * @return Returns the connection strength in range 0 to 4.
      */
-    @IntRange(from = 0, to = 3)
+    @IntRange(from = 0, to = 4)
     public int getConnectionStrength() {
         return mConnectionStrength;
     }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index e5ef62b..feef049 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -35,6 +35,7 @@
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.R;
@@ -173,10 +174,15 @@
                     R.string.config_sharedConnectivityServicePackage);
             String serviceIntentAction = resources.getString(
                     R.string.config_sharedConnectivityServiceIntentAction);
+            if (TextUtils.isEmpty(servicePackageName) || TextUtils.isEmpty(serviceIntentAction)) {
+                Log.e(TAG, "To support shared connectivity service on this device, the"
+                        + " service's package name and intent action strings must not be empty");
+                return null;
+            }
             return new SharedConnectivityManager(context, servicePackageName, serviceIntentAction);
         } catch (Resources.NotFoundException e) {
             Log.e(TAG, "To support shared connectivity service on this device, the service's"
-                    + " package name and intent action string must be defined");
+                    + " package name and intent action strings must be defined");
         }
         return null;
     }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
index b585bd5..a03a6c2 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -105,6 +105,13 @@
     }
 
     @Test
+    public void resourceStringsAreEmpty_createShouldReturnNull() {
+        when(mResources.getString(anyInt())).thenReturn("");
+
+        assertThat(SharedConnectivityManager.create(mContext)).isNull();
+    }
+
+    @Test
     public void bindingToServiceOnFirstCallbackRegistration() {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.registerCallback(mExecutor, mClientCallback);